{"id":1,"category":"Getting Started","question":"What does a minimal EK9 program look like?","url":"https://ek9.io/qa/QA0001.html","alternatePhrasings":["Hello World in EK9","Simplest EK9 program","EK9 getting started example"],"answer":"A minimal EK9 program requires three things:\n1. A module declaration (`defines module`)\n2. A program construct (`defines program`)\n3. A program entry point (a named function inside `defines program`)\n\nThe program name becomes the entry point. EK9 uses indentation (2 spaces) to define scope — no braces needed.\n\nTo compile and run:\n  ek9 -c hello.ek9    (compile)\n  ek9 hello.ek9       (run)\n\nFor the full Stdout API, run: ek9 -h Stdout\n\nSee Q3 for printing output. See Q5 for source file structure. See Q17 for entry points and defines program. See Q93 for defining classes. See Q19 for scripting.","ek9Example":"defines module qa.getting.started.minimal\n\n  defines program\n    MinimalProgram()\n      stdout <- Stdout()\n      stdout.println(\"Hello, World!\")","migrationContext":"All — first thing everyone asks","keywords":["basic","beginner","first","hello","intro","minimal","program","simple","start","world"],"primaryTopics":["hello world","minimal example","first ek9","getting started"],"typicalErrors":[{"error":"E01040","correct":"defines program\n    MinimalProgram()\n      stdout <- Stdout()\n      stdout.println(\"Hello, World!\")","incorrect":"defines program\n    MinimalProgram()\n      stdout <- Stdout()\n      stdout.println(\"Hello, World!\")\n    MinimalProgram()\n      stdout <- Stdout()","explanation":"Defining two programs with the same name in the same module triggers E01040 — duplicate type definition. Each program must have a unique name within its module. See ek9 -h E01040 for details."}],"companions":[],"oracleToolHint":{"tool":"ek9_scaffold","intent":"program","description":"Oracle can generate a program construct with correct module wrapping, entry point, and Stdout setup."}}
{"id":2,"category":"Getting Started","question":"How do I compile and run an EK9 program?","url":"https://ek9.io/qa/QA0002.html","alternatePhrasings":["How to build and execute EK9 code","EK9 compile command","Run an EK9 file from the command line"],"answer":"EK9 files start with #!ek9, so on Linux/macOS you can run them directly:\n  chmod u+x hello.ek9\n  ./hello.ek9\nThis auto-compiles and runs in one step.\n\nAlternatively, use the ek9 command explicitly:\n  ek9 hello.ek9           compile and run\n  ek9 -c hello.ek9        compile only (incremental)\n  ek9 -C hello.ek9        full recompile from scratch\n\nIf a file contains multiple programs, specify which to run:\n  ek9 -r ProgramName hello.ek9\n\nCompilation variants:\n  ek9 -cg hello.ek9       compile with debug information\n  ek9 -cd hello.ek9       compile with dev code and debug info\n\nAdditional modes:\n  ek9 -d 5005 hello.ek9   run with debugger on port 5005\n  ek9 -t hello.ek9        run tests from dev/ directory\n  ek9 -E3 hello.ek9       rich error messages with fix suggestions\n\nFor the full list of CLI options, run: ek9 -h\n\nSee Q1 for a minimal program. See Q12 for installation. See Q21 for the built-in help system.","ek9Example":"defines module qa.getting.started.compile.and.run\n\n  defines program\n    CompileAndRun()\n      stdout <- Stdout()\n      stdout.println(\"Program compiled and running!\")","migrationContext":"All languages have build toolchains: Python uses python/pip, Java uses javac/Maven/Gradle, Rust uses cargo build/run, Go uses go build/run. EK9 unifies compilation, execution, testing, packaging, and dependency management into a single ek9 command.","keywords":["beginner","build","cli","command","compile","debug","execute","first","flags","incremental","intro","program","recompile","run","start"],"primaryTopics":["compile","run program","execute","build and run"],"typicalErrors":[{"error":"E50060","correct":"stdout.println(\"Program compiled and running!\")","incorrect":"stdout.printLine(\"Program compiled and running!\")","explanation":"EK9 Stdout uses println() for output with newline. The method printLine() does not exist on Stdout — AI trained on other languages often generates this. Triggers E50060 — method not resolved. See ek9 -h Stdout for the full API."}],"companions":[]}
{"id":3,"category":"Getting Started","question":"How do I print output to the console?","url":"https://ek9.io/qa/QA0003.html","alternatePhrasings":["How to print text in EK9","EK9 equivalent of println","How to display output in EK9"],"answer":"Use the built-in Stdout type:\n  stdout <- Stdout()\n  stdout.println(\"Hello, World!\")\n\nprintln() adds a newline, print() does not:\n  stdout.print(\"no newline\")\n  stdout.println(\" with newline\")\n\nString interpolation uses backticks with ${}:\n  name <- \"Steve\"\n  stdout.println(`Hello ${name}`)\n\nConcatenation uses the + operator:\n  stdout.println(\"Value is [\" + name + \"]\")\n\nAny type with a $ (string) operator can be printed directly.\n\nFor error/diagnostic output, use Stderr instead:\n  stderr <- Stderr()\n  stderr.println(\"Something went wrong\")\n\nIn stream pipelines, stdout is a sink:\n  cat items > stdout\n\nFor the full Stdout API, run: ek9 -h Stdout\n\nSee Q4 for reading input. See Q37 for string interpolation and methods. See Q43 for escaping in interpolated strings.","ek9Example":"defines module qa.getting.started.print.output\n\n  defines program\n    PrintOutput()\n      stdout <- Stdout()\n\n      stdout.println(\"Hello, World!\")\n      stdout.print(\"no \")\n      stdout.println(\"newline needed\")\n\n      name <- \"EK9\"\n      stdout.println(`Welcome to ${name}`)\n      stdout.println(`Language: [${name}]`)","migrationContext":"Python: print(), Java: System.out.println(), Rust: println!(), Go: fmt.Println(), C++: std::cout <<. EK9 uses an object-based approach — create Stdout() and call methods on it.","keywords":["beginner","console","display","first","intro","output","print","println","screen","start","stdout","text","write"],"primaryTopics":["print","console output","stdout","println"],"typicalErrors":[{"error":"E50001","correct":"stdout.println(`Welcome to ${name}`)","incorrect":"stdout.println(`Welcome to ${nme}`)","explanation":"A typo in the variable name 'nme' means the compiler cannot find any identifier with that name. The correct name is 'name'. EK9 is case-sensitive. See ek9 -h E50001 for details."}],"companions":[]}
{"id":4,"category":"Getting Started","question":"How do I read input from the user?","url":"https://ek9.io/qa/QA0004.html","alternatePhrasings":["How to read from stdin in EK9","EK9 equivalent of input or scanf","How to get user input in EK9"],"answer":"Use the built-in Stdin type:\n  stdin <- Stdin()\n\nRead a single line with a guard (only executes if input is available):\n  if line <- stdin.next()\n    stdout.println(`You entered: ${line}`)\n\nIterate over all lines until EOF:\n  while stdin?\n    stdout.println(stdin.next())\n\nIn a stream pipeline, stdin is a source:\n  cat stdin | filter by notEmpty > stdout\n\nStdin implements the StringInput trait with these methods:\n  hasNext()    check if more input is available\n  next()       get the next line as String\n  ?            isSet operator, true when input available\n\nStdin works with both interactive input and piped data:\n  echo \"Hello\" | ek9 myProgram.ek9\n  cat data.txt | ./myProgram.ek9\n  ls -la | ek9 myProgram.ek9\nThe same program handles all three cases without any changes.\n\nWhen piped input ends or the user sends EOF (Ctrl+D on Unix, Ctrl+Z on Windows), hasNext() returns false.\n\nFor the full Stdin API, run: ek9 -h Stdin\n\nSee Q3 for printing output. See Q37 for string operations on input lines.","ek9Example":"defines module qa.getting.started.read.input\n\n  defines program\n    ReadInput()\n      stdin <- Stdin()\n      stdout <- Stdout()\n\n      stdout.println(\"Type something and press Enter:\")\n      if line <- stdin.next()\n        stdout.println(`You said: ${line}`)","migrationContext":"Python: input(), Java: Scanner(System.in), Rust: stdin().read_line(), Go: fmt.Scanln() or bufio.NewReader. EK9 uses a guard pattern with Stdin — the compiler ensures you only process input when it is available.","keywords":["beginner","console","first","input","interactive","intro","keyboard","pipe","read","readline","start","stdin","user"],"primaryTopics":["read input","user input","stdin","keyboard input"],"typicalErrors":[{"error":"E50060","correct":"if line <- stdin.next()","incorrect":"if line <- stdin.readLine()","explanation":"EK9 Stdin uses next() to read the next line, not readLine(). AI often uses Java-style method names. Triggers E50060 — method not resolved. See ek9 -h Stdin for the full API."},{"error":"E08020","correct":"stdin <- Stdin()","incorrect":"stdin as Stdin?","explanation":"Declaring stdin without initialising it (as Stdin?) then calling stdin.next() uses an uninitialised variable as a method receiver. Triggers E08020 — might be used before being initialised. Always initialise with <- Stdin(). See ek9 -h E08020 for details."}],"companions":[]}
{"id":5,"category":"Getting Started","question":"How are EK9 source files structured?","url":"https://ek9.io/qa/QA0005.html","alternatePhrasings":["What is the structure of an EK9 file?","How do I organize an EK9 source file?","What sections does an EK9 file have?"],"answer":"Every EK9 source file follows this structure:\n\n1. Shebang line: #!ek9 (required first line, enables direct execution on Unix)\n2. Optional comment header between doc comment markers\n3. Module declaration: `defines module <dotted.name>`\n4. Optional `references` block (imports from other modules)\n5. One or more construct blocks inside the module\n\nIdiomatic ordering of construct blocks (top to bottom):\n  defines type        type aliases and constrained types\n  defines constant    named constant values\n  defines record      data-only types (fields, constructors, operators)\n  defines trait       interfaces for polymorphism\n  defines class       full object types with methods and operators\n  defines function    standalone functions\n  defines component   dependency-injected types\n  defines text        internationalized text templates\n  defines service     web service endpoints\n  defines application wires components together\n  defines program     executable entry points (always last)\n\nWithin aggregates (classes, records, traits) the ordering is enforced by the grammar:\n  1. Properties/fields\n  2. Constructors and methods\n  3. Operators\n  4. default operator (generates synthetic operators like ==, <>, $, #?)\n\nStyle guidelines:\n- Use only ONE `defines <construct>` block per type per file (avoid scattered blocks)\n- Multiple files CAN share the same module name, splitting a module across files by concern\n- Directory structure is your choice (unlike Java, the module name does not dictate the path)\n\nOne module per file. The module name is the namespace for everything defined in it.\n\nFor module organization, run: ek9 -q organize code modules\n\nSee Q6 for organizing code into modules. See Q16 for indentation rules. See Q17 for entry points with defines program. See Q18 for comment styles. See Q20 for file extension.","ek9Example":"defines module qa.getting.started.source.file.structure\n\n  defines constant\n    Greeting <- \"Hello from EK9\"\n\n  defines function\n    decorateMessage() as pure\n      -> message as String\n      <- result as String: `[${message}]`\n\n  defines record\n    Person\n      name as String: String()\n\n      Person()\n        -> n as String\n        name :=: n\n\n      default operator ?\n\n      operator $ as pure\n        <- rtn as String: name\n\n  defines program\n    ShowStructure()\n      stdout <- Stdout()\n      person <- Person(\"Steve\")\n      decorated <- decorateMessage($person)\n      stdout.println(Greeting)\n      stdout.println(decorated)","migrationContext":"Python: modules with imports and classes/functions at top level. Java: one public class per file with package declaration and directory must match package name. Rust: mod/crate system with use statements. Go: package declaration with func/type/var. Unlike Java, EK9 does not require directory structure to match module names.","keywords":["beginner","construct","defines","file","first","intro","layout","migrate","module","ordering","organize","section","source","start","structure"],"primaryTopics":["source file structure","file structure","program structure"],"typicalErrors":[{"error":"E08180","correct":"name as String: String()","incorrect":"name as String","explanation":"Class and record fields must be initialised inline. Leaving a field uninitialised produces E08180. Use an explicit constructor call or literal value. See ek9 -h E08180 for details."},{"error":"E07520","correct":"default operator ?","incorrect":"operator ?","explanation":"The ? operator is inherited from the base and must use 'override' or be generated with 'default'. Declaring 'operator ?' without 'default' or 'override' triggers E07520 because the bare declaration lacks proper return semantics. See ek9 -h E07520 for details."},{"error":"E01030","correct":"decorateMessage() as pure\n      -> message as String\n      <- result as String: `[${message}]`","incorrect":"Person() as pure\n      -> message as String\n      <- result as String: `[${message}]`","explanation":"Defining a function with the same name as a record in the same module creates a name collision between different symbol kinds. Triggers E01030 — variable/function/type duplicated. Each name must be unique within its module. See ek9 -h E01030 for details."}],"companions":[]}
{"id":6,"category":"Getting Started","question":"How do I organize code into modules?","url":"https://ek9.io/qa/QA0006.html","alternatePhrasings":["How do modules work in EK9?","How do I import from another module?","What is the references keyword?","How do I access code from a different module?","How do I split code across files?"],"answer":"Every EK9 source file declares exactly one module with `defines module <dotted.name>`.\n\nThree key concepts:\n\n1. Same module, multiple files:\n   Multiple .ek9 files can share the same module name. Everything in the same module is automatically visible across all files sharing that name - no import needed.\n\n2. Cross-module access with `references`:\n   To use a symbol from another module, add a `references` block after the module declaration:\n     references\n       other.module::SomeType\n       other.module::someFunction\n   Then use `SomeType` and `someFunction` directly in your code.\n\n3. Fully qualified names (no import):\n   You can skip `references` and use the full path inline:\n     result <- other.module::someFunction(value)\n\nKey rules:\n- Module names must be lowercase with dot separators\n- References must list each symbol explicitly (no wildcards)\n- `::` separates the module name from the symbol name\n- References must appear before the first construct block\n- References must be in alphabetical order (E11026 if not)\n- Module hierarchy is flat: `com.foo.bar` has no special access to `com.foo`\n- Directory structure is your choice (unlike Java, module name does not dictate path)\n- One module per file (each file declares exactly one module)\n\nThe deliberate absence of wildcards forces you to think about coupling. If your references list is getting long, it may be time to refactor.\n\nThe code example below shows a well-organized module with constants, functions, records, and a program all working together within a single module.\n\nSee Q5 for source file structure. See Q7 for managing dependencies. See Q13 for the difference between packages and modules.","ek9Example":"defines module qa.getting.started.organize.modules\n\n  defines constant\n    PI <- 3.142\n\n  defines function\n    areaOfCircle() as pure\n      -> diameter as Float\n      <- area as Float: PI * (diameter / 2.0) ^ 2\n\n  defines record\n    Circle\n      diameter as Float: Float()\n\n      Circle()\n        -> d as Float\n        diameter :=: d\n\n      default operator ?\n\n      operator $ as pure\n        <- rtn as String: `Circle(diameter=${diameter})`\n\n  defines program\n    OrganizeModules()\n      stdout <- Stdout()\n\n      circle <- Circle(10.0)\n      area <- areaOfCircle(circle.diameter)\n\n      stdout.println(\"Module organization example\")\n      stdout.println($circle)\n      stdout.println(`Area: ${area}`)","migrationContext":"Python: `import module` or `from module import name`. Java: `package` declaration with `import` statements (directory must match package). Rust: `mod`/`use` with `crate::` paths. Go: `package` with `import`. Unlike Java, EK9 does not require directory structure to match module names. Unlike Python/Java, no wildcard imports allowed.","keywords":["beginner","cross-module","files","first","import","intro","migrate","module","namespace","organize","package","references","split","start","structure"],"primaryTopics":["module","organize code","package","namespace"],"typicalErrors":[{"error":"E08180","correct":"diameter as Float: Float()","incorrect":"diameter as Float","explanation":"Record fields must be initialised inline. An uninitialised field triggers E08180. Use a constructor call like Float() or a literal value. See ek9 -h E08180 for details."},{"error":"E01040","correct":"circle <- Circle(10.0)","incorrect":"Circle <- Circle(10.0)","explanation":"Declaring a variable with the same name as a type defined in the module or imported via references creates a conflict. Rename the variable to avoid ambiguity with the type name. See ek9 -h E01040 for details."}],"companions":[]}
{"id":7,"category":"Getting Started","question":"How do I manage dependencies in EK9?","url":"https://ek9.io/qa/QA0007.html","alternatePhrasings":["How do I add a library dependency?","How do I declare package dependencies?","What is defines package in EK9?","How does EK9 handle third-party libraries?","How do I publish or deploy an EK9 package?","What replaces pom.xml or build.gradle in EK9?","I use Maven/Gradle for dependencies, how does EK9 handle this?"],"answer":"EK9 manages dependencies as a first-class language feature inside `defines package`.\n\nNo separate build file (pom.xml, Cargo.toml, go.mod) is needed. Everything lives in your .ek9 source file.\n\nPackage metadata:\n  version as Version: 1.0.0-0          semantic version with build number\n  description as String = \"...\"         what the package does\n  license <- \"MIT\"                      license identifier\n  tags <- [ \"tools\", \"networking\" ]     categorization tags\n  publicAccess <- true                  visible to other packages\n\nDependencies use Dict literals:\n  deps <- {\n    \"ekopen.network.utils\": \"1.6.1-9\"\n  }\n\nDev-only dependencies (not included in production):\n  devDeps <- {\n    \"ekopen.test.helpers\": \"2.0.0-0\"\n  }\n\nExclude transitive dependencies:\n  excludeDeps <- {\n    \"unwanted.transitive.dep\": \"ekopen.network.utils\"\n  }\n\nCLI commands for the dependency lifecycle:\n  ek9 -Dp app.ek9             resolve dependencies (cleans first)\n  ek9 -Dp -v app.ek9          resolve and show accepted/rejected deps\n  ek9 -I app.ek9              install to local library cache\n  ek9 -P app.ek9              package for deployment (uses -O3)\n  ek9 -D app.ek9              deploy (triggers -P and -Gk if needed)\n  ek9 -Gk app.ek9             generate signing keys\n\nVersion management:\n  ek9 -IV minor app.ek9       increment minor version\n  ek9 -SV 2.0.0 app.ek9       set version explicitly\n  ek9 -PV app.ek9             print current version\n\nVersion format: MAJOR.MINOR.PATCH-BUILD (e.g., 1.0.0-0)\nFeature branches: MAJOR.MINOR.PATCH-FEATURE-BUILD (e.g., 1.0.0-beta-5)\n\nSemantic versioning is enforced:\n- Same MAJOR.MINOR: highest PATCH is auto-selected\n- Different MAJOR: build fails (breaking API change)\n- Circular dependencies are detected and rejected\n\nThe code example below shows a complete package definition with dependencies.\n\nSee Q8 for tagging packages for discovery. See Q9 for licensing. See Q10 for dev vs production dependencies. See Q11 for excluding transitive dependencies. See Q13 for packages vs modules. See Q14 for listing resolved dependencies. See Q15 for semantic versioning. See Q270 for supply chain security.","ek9Example":"defines module qa.getting.started.manage.dependencies\n\n  defines package\n\n    version as Version: 1.0.0-0\n    description as String = \"Example package showing dependency management\"\n    license <- \"MIT\"\n    publicAccess <- true\n\n    tags <- [\n      \"example\",\n      \"getting-started\"\n    ]\n\n    applyStandardIncludes <- true\n\n    deps <- {\n      \"ekopen.network.support.utils\": \"1.6.1-9\",\n      \"ekopen.net.handy.tools\": \"3.2.1-0\"\n    }\n\n    devDeps <- {\n      \"ekopen.org.supertools.util\": \"4.6.1-6\"\n    }\n\n    excludeDeps <- {\n      \"ekopen.some.unwanted.pack\": \"ekopen.org.supertools.util\"\n    }\n\n  defines program\n    ManageDependencies()\n      stdout <- Stdout()\n\n      stdout.println(\"Package with dependencies defined\")\n      stdout.println(\"Run: ek9 -Dp thisFile.ek9 to resolve\")\n      stdout.println(\"Run: ek9 -PV thisFile.ek9 to see version\")","migrationContext":"Python: pip/requirements.txt/pyproject.toml. Java: Maven pom.xml or Gradle build.gradle. Rust: Cargo.toml [dependencies]. Go: go.mod require directives. Node: package.json dependencies. EK9 embeds all of this directly in the source file as a language construct, not a separate configuration format.","keywords":["beginner","cargo","dependencies","deploy","deps","devDeps","first","gradle","import","install","intro","library","maven","mvn","npm","package","pip","pom","publish","semantic","start","version","versioning"],"primaryTopics":["import library","external package","third party library","defines package"],"typicalErrors":[],"companions":[]}
{"id":8,"category":"Getting Started","question":"How do I tag my package so it can be easily found in remote repositories?","url":"https://ek9.io/qa/QA0008.html","alternatePhrasings":["How do I make my EK9 package discoverable?","What are tags in defines package?","How do I categorize my EK9 library?","How do package tags work?","What is the EK9 equivalent of Cargo.toml keywords or PyPI classifiers?"],"answer":"EK9 packages include a `tags` field inside `defines package` that accepts a List of String values. These tags serve as searchable metadata in remote repositories, helping other developers discover your package.\n\nDeclare tags as a List literal:\n  tags <- [\n    \"networking\",\n    \"http\",\n    \"rest-client\"\n  ]\n\nTag best practices:\n- Use lowercase with hyphens for multi-word tags\n- Include the problem domain (\"networking\", \"database\", \"parsing\")\n- Include the technology area (\"http\", \"json\", \"csv\")\n- Include the package role (\"client\", \"server\", \"utility\", \"middleware\")\n- Keep tags specific: \"http-client\" is more useful than \"tools\"\n- Limit to 5-10 tags: too many dilutes discoverability\n\nCombined with `description` and `publicAccess`, tags form the discovery profile:\n  description as String = \"Lightweight HTTP client with retry and timeout support\"\n  publicAccess <- true\n  tags <- [ \"http\", \"client\", \"networking\", \"rest\" ]\n\nSetting `publicAccess <- false` makes the package private: it can still be deployed to a private repository but will not appear in public searches.\n\nThe code example below shows a well-tagged package ready for repository discovery.\n\nSee Q7 for managing dependencies. See Q9 for licensing. See Q13 for packages vs modules.","ek9Example":"defines module qa.getting.started.tag.packages\n\n  defines package\n\n    version as Version: 1.0.0-0\n    description as String = \"Lightweight HTTP client with retry and timeout support\"\n    license <- \"MIT\"\n    publicAccess <- true\n\n    tags <- [\n      \"http\",\n      \"client\",\n      \"networking\",\n      \"rest\",\n      \"retry\"\n    ]\n\n    applyStandardIncludes <- true\n\n  defines program\n    TagPackages()\n      stdout <- Stdout()\n\n      stdout.println(\"Package tagged for repository discovery\")\n      stdout.println(\"Tags help other developers find your library\")","migrationContext":"Python: PyPI classifiers and keywords in pyproject.toml. Java: Maven pom.xml has no standard tags (relies on artifact naming). Rust: Cargo.toml keywords array (max 5). npm: package.json keywords array. Go: no package-level tags (relies on pkg.go.dev indexing). EK9 tags are a simple List literal in the source file.","keywords":["beginner","cargo","categorize","discoverable","first","intro","keywords","library","metadata","npm","package","publicAccess","pypi","repository","search","start","tags"],"primaryTopics":[],"typicalErrors":[{"error":"E01040","correct":"defines program\n    TagPackages()\n      stdout <- Stdout()","incorrect":"defines program\n    TagPackages()\n      stdout <- Stdout()\n    TagPackages()\n      stdout <- Stdout()","explanation":"Defining two constructs with the same name in the same module triggers E01040 — duplicate construct. Each program, class, or function name must be unique within its module. See ek9 -h E01040 for details."}],"companions":[]}
{"id":9,"category":"Getting Started","question":"How can I control the license agreement for my package's code?","url":"https://ek9.io/qa/QA0009.html","alternatePhrasings":["How do I set a license on my EK9 package?","What licenses can I use for EK9 packages?","How does licensing work in defines package?","How do I make my EK9 code open source?","I set license in Cargo.toml or package.json, how do I do it in EK9?"],"answer":"EK9 packages declare their license inside `defines package` using the `license` field. This is a String value that specifies the terms under which others may use your code.\n\nDeclare the license:\n  license <- \"MIT\"\n\nCommon open source licenses:\n  \"MIT\"            permissive, minimal restrictions\n  \"Apache-2.0\"     permissive with patent grant\n  \"GPL-3.0\"        copyleft, derivatives must also be GPL\n  \"BSD-2-Clause\"   permissive, simple\n  \"LGPL-3.0\"       copyleft for libraries, linking permitted\n  \"MPL-2.0\"        file-level copyleft\n\nFor proprietary code:\n  \"Proprietary\"    or your company's license name\n\nThe license field works together with `publicAccess`:\n  publicAccess <- true     anyone can download and use\n  publicAccess <- false    private, restricted distribution\n\nA package with `publicAccess <- true` and `license <- \"MIT\"` tells consumers they can freely use, modify, and redistribute the code with attribution.\n\nA package with `publicAccess <- false` and `license <- \"Proprietary\"` is restricted to authorized users only.\n\nThe license value is metadata: EK9 does not enforce license compliance at compile time. It is your responsibility to ensure your dependencies' licenses are compatible with your own. The `description` field can include additional terms if needed.\n\nThe code example below shows two typical licensing configurations.\n\nSee Q7 for managing dependencies. See Q8 for tagging packages.","ek9Example":"defines module qa.getting.started.package.licensing\n\n  defines package\n\n    version as Version: 1.0.0-0\n    description as String = \"Open source utility with MIT license\"\n    license <- \"MIT\"\n    publicAccess <- true\n\n    tags <- [\n      \"utility\",\n      \"open-source\"\n    ]\n\n    applyStandardIncludes <- true\n\n  defines program\n    PackageLicensing()\n      stdout <- Stdout()\n\n      stdout.println(\"Package license: MIT\")\n      stdout.println(\"Public access: true\")\n      stdout.println(\"Anyone can use, modify, and redistribute with attribution\")","migrationContext":"Python: pyproject.toml license field or LICENSE file. Java: Maven pom.xml <licenses> section. Rust: Cargo.toml license field (SPDX identifier). npm: package.json license field. Go: LICENSE file by convention. EK9 uses a simple String field in the source file, similar to Rust's approach.","keywords":["MIT","beginner","cargo","copyright","distribution","first","intro","license","npm","open-source","package","proprietary","publicAccess","spdx","start","terms"],"primaryTopics":[],"typicalErrors":[{"error":"E01040","correct":"defines program\n    PackageLicensing()\n      stdout <- Stdout()","incorrect":"defines program\n    PackageLicensing()\n      stdout <- Stdout()\n    PackageLicensing()\n      stdout <- Stdout()","explanation":"Defining two constructs with the same name in the same module triggers E01040 — duplicate construct. Each program name must be unique. See ek9 -h E01040 for details."}],"companions":[]}
{"id":10,"category":"Getting Started","question":"How do I distinguish between development dependencies and production dependencies?","url":"https://ek9.io/qa/QA0010.html","alternatePhrasings":["What is the difference between deps and devDeps?","How do I add test-only dependencies?","What are development dependencies in EK9?","How do I keep test libraries out of production?","What is the EK9 equivalent of Maven scope=test or Gradle testImplementation?","I use npm devDependencies, how does EK9 separate dev from production?"],"answer":"EK9 separates dependencies into two groups inside `defines package`:\n\n`deps` - production dependencies:\n  deps <- {\n    \"ekopen.network.utils\": \"1.6.1-9\"\n  }\n  These are required at runtime. When someone depends on YOUR package, they also get your `deps` as transitive dependencies. These are included in packaged artifacts (-P).\n\n`devDeps` - development-only dependencies:\n  devDeps <- {\n    \"ekopen.test.helpers\": \"2.0.0-0\"\n  }\n  These are only available during development and testing. They are NOT included when your package is deployed or when others depend on your package. Test frameworks, mock libraries, and development tools belong here.\n\nKey differences:\n- `deps` are transitive: your consumers inherit them\n- `devDeps` are NOT transitive: your consumers never see them\n- `deps` are included in -P (package) and -D (deploy)\n- `devDeps` are excluded from packaged artifacts\n- `deps` are resolved by -Dp for everyone\n- `devDeps` are only resolved for the package owner\n\nRule of thumb:\n- If your code needs it to RUN: put it in `deps`\n- If your code only needs it to TEST or BUILD: put it in `devDeps`\n\nBoth use the same Dict literal syntax with module name to version mapping.\n\nThe code example below shows a package with both production and development dependencies clearly separated.\n\nSee Q7 for managing dependencies. See Q11 for excluding transitive dependencies.","ek9Example":"defines module qa.getting.started.dev.vs.production.deps\n\n  defines package\n\n    version as Version: 1.0.0-0\n    description as String = \"Shows deps vs devDeps separation\"\n    license <- \"MIT\"\n    publicAccess <- true\n\n    tags <- [\n      \"example\"\n    ]\n\n    applyStandardIncludes <- true\n\n    //Production dependencies: included in deployed package\n    deps <- {\n      \"ekopen.network.support.utils\": \"1.6.1-9\",\n      \"ekopen.net.handy.tools\": \"3.2.1-0\"\n    }\n\n    //Development-only: test frameworks, mocks, tools\n    devDeps <- {\n      \"ekopen.org.supertools.util\": \"4.6.1-6\",\n      \"ekopen.org.net.tools.misc\": \"3.2.3-21\"\n    }\n\n  defines program\n    DevVsProductionDeps()\n      stdout <- Stdout()\n\n      stdout.println(\"deps: included in production, transitive to consumers\")\n      stdout.println(\"devDeps: development only, never shipped to consumers\")","migrationContext":"Python: pip has no built-in distinction (extras_require for dev). Java: Maven scope=test or Gradle testImplementation. Rust: Cargo.toml [dev-dependencies]. npm: package.json devDependencies. Go: no formal distinction. EK9 uses deps and devDeps as parallel Dict literals in the source file.","keywords":["beginner","dependencies","deploy","deps","devDependencies","devDeps","development","first","gradle","intro","maven","npm","package","production","scope","start","test","testImplementation","transitive"],"primaryTopics":[],"typicalErrors":[{"error":"E01040","correct":"defines program\n    DevVsProductionDeps()\n      stdout <- Stdout()","incorrect":"defines program\n    DevVsProductionDeps()\n      stdout <- Stdout()\n    DevVsProductionDeps()\n      stdout <- Stdout()","explanation":"Defining two constructs with the same name in the same module triggers E01040 — duplicate construct. Each program name must be unique within the module. See ek9 -h E01040 for details."}],"companions":[]}
{"id":11,"category":"Getting Started","question":"What if there are transitive dependencies I do not want to use?","url":"https://ek9.io/qa/QA0011.html","alternatePhrasings":["How do I exclude a transitive dependency?","What is excludeDeps in EK9?","How do I block an unwanted indirect dependency?","How do I prevent a dependency of a dependency from being included?","What is the EK9 equivalent of Maven exclusions or Gradle exclude?"],"answer":"When your package depends on library A, and library A depends on library B, then B is a transitive dependency. Sometimes you need to exclude B: it may conflict with another version, have a security vulnerability, or pull in functionality you do not need.\n\nEK9 provides `excludeDeps` inside `defines package` for this:\n  excludeDeps <- {\n    \"unwanted.module.name\": \"parent.that.brings.it.in\"\n  }\n\nThe key is the module you want to EXCLUDE. The value is the module that DEPENDS on it (the one pulling it in as a transitive dependency).\n\nExample scenario:\n  Your package depends on `ekopen.network.utils` version 1.6.1-9.\n  That library depends on `ekopen.old.logging` version 0.9.0-0.\n  You do not want `ekopen.old.logging`. So you write:\n\n  excludeDeps <- {\n    \"ekopen.old.logging\": \"ekopen.network.utils\"\n  }\n\nThis tells the dependency resolver: when processing `ekopen.network.utils`, skip its dependency on `ekopen.old.logging`.\n\nEK9 also protects you automatically:\n- Circular dependencies are detected and rejected at resolve time\n- Version rationalization selects the highest compatible PATCH version\n- Major version conflicts (different MAJOR numbers) fail the build\n- Duplicate dependency entries are handled gracefully\n\nRun `ek9 -Dp -v app.ek9` to resolve and see which dependencies were accepted and which were rejected (with reasons like MANUAL, RATIONALISATION, OPTIMISED).\n\nThe code example below shows a package that excludes an unwanted transitive dependency.\n\nSee Q7 for managing dependencies. See Q10 for dev vs production dependencies. See Q14 for listing resolved dependencies.","ek9Example":"defines module qa.getting.started.exclude.transitive.deps\n\n  defines package\n\n    version as Version: 1.0.0-0\n    description as String = \"Shows how to exclude transitive dependencies\"\n    license <- \"Apache-2.0\"\n    publicAccess <- true\n\n    tags <- [\n      \"example\"\n    ]\n\n    applyStandardIncludes <- true\n\n    deps <- {\n      \"ekopen.network.support.utils\": \"1.6.1-9\",\n      \"ekopen.net.handy.tools\": \"3.2.1-0\"\n    }\n\n    //Exclude: block ekopen.some.unwanted.pack from being pulled in\n    //via ekopen.network.support.utils\n    excludeDeps <- {\n      \"ekopen.some.unwanted.pack\": \"ekopen.network.support.utils\"\n    }\n\n  defines program\n    ExcludeTransitiveDeps()\n      stdout <- Stdout()\n\n      stdout.println(\"Transitive dependency excluded\")\n      stdout.println(\"Run: ek9 -Dp thisFile.ek9 to resolve and verify\")","migrationContext":"Python: pip has no built-in exclusion (manual override). Java: Maven <exclusions> inside <dependency>, Gradle exclude group/module. Rust: no direct exclusion (use [patch] or fork). npm: overrides field. Go: go.mod exclude directive. EK9 uses excludeDeps as a Dict mapping excluded module to its parent.","keywords":["beginner","conflict","dependency","exclude","excludeDeps","exclusions","first","gradle","indirect","intro","maven","resolve","start","transitive","unwanted","version"],"primaryTopics":[],"typicalErrors":[],"companions":[]}
{"id":12,"category":"Getting Started","question":"How do I install EK9?","url":"https://ek9.io/qa/QA0012.html","alternatePhrasings":["What are the prerequisites for EK9?","How do I set up EK9 on my machine?","What do I need to run the EK9 compiler?","How do I get started with EK9 installation?"],"answer":"EK9 requires Java 25 or later. The recommended JDK is Azul Zulu (https://www.azul.com/downloads/) which provides free, production-ready builds for all platforms.\n\nInstallation steps:\n\n1. Install Java 25+:\n   Download and install Azul Zulu JDK 25 (or any Java 25+ JDK). Verify with:\n     javac -version\n   Must show version 25 or higher.\n\n2. Get the EK9 compiler:\n   The compiler is distributed as two files:\n   - `ek9` (or `ek9.exe` on Windows): a lightweight native wrapper\n   - `ek9c-jar-with-dependencies.jar`: the compiler itself\n\n   Place both in the same directory and add that directory to your PATH.\n\n3. Verify installation:\n     ek9 -V                    show compiler version\n     ek9 -h                    show help\n     ek9 -H                    list all help keywords\n\nAlternative setup with EK9_HOME:\n   Instead of placing both files together, set the EK9_HOME environment variable to point to the directory containing the JAR:\n     export EK9_HOME=/opt/ek9\n   The `ek9` wrapper checks EK9_HOME first, then looks relative to its own location.\n\nMemory configuration:\n   EK9_COMPILER_MEMORY=\"-Xmx1g\"       compiler memory (default: 512m)\n   EK9_APPLICATION_MEMORY=\"-Xmx2g\"    program memory (default: 512m)\n\nFor compiler developers (building from source):\n     git clone https://github.com/stephenjohnlimb/ek9.git\n     cd ek9\n     mvn clean install\n   The JAR appears at compiler-cli/target/ek9c-jar-with-dependencies.jar\n   The native wrapper is built via CMake during the Maven build.\n\nOn Linux/macOS, EK9 files with the #!ek9 shebang can run directly:\n     chmod u+x hello.ek9\n     ./hello.ek9\n\nThe code example below is a quick installation verification program.\n\nSee Q2 for compiling and running programs. See Q21 for the built-in help system. See Q252 for verbose and debug compilation modes.","ek9Example":"defines module qa.getting.started.install.ek9\n\n  defines program\n    VerifyInstallation()\n      stdout <- Stdout()\n\n      stdout.println(\"EK9 is installed and working!\")\n      stdout.println(\"Try: ek9 -V for version info\")\n      stdout.println(\"Try: ek9 -h String for type help\")\n      stdout.println(\"Try: ek9 -H for all help keywords\")","migrationContext":"Python: python.org installer or system package manager. Java: JDK download from Oracle/Azul/Adoptium. Rust: rustup one-line installer. Go: go.dev/dl installer. Node: nodejs.org or nvm. EK9 requires a Java 25+ JDK as its runtime platform, with a native C wrapper providing a simple command-line interface.","keywords":["Azul","EK9_HOME","JDK","Java","PATH","beginner","download","first","install","intro","migrate","prerequisites","setup","start","version"],"primaryTopics":["install","setup","download","get started"],"typicalErrors":[{"error":"E50060","correct":"stdout.println(\"EK9 is installed and working!\")","incorrect":"stdout.writeLine(\"EK9 is installed and working!\")","explanation":"EK9 Stdout uses println() for output, not writeLine() or write(). AI often generates method names from other languages. Triggers E50060 — method not resolved. See ek9 -h Stdout for the full API."}],"companions":[]}
{"id":13,"category":"Getting Started","question":"What is the difference between a package and a module in EK9?","url":"https://ek9.io/qa/QA0013.html","alternatePhrasings":["When do I use defines module vs defines package?","Do I need both a module and a package?","Is a package the same as a module in EK9?","How do modules and packages relate to each other?"],"answer":"Modules and packages serve different purposes in EK9.\n\nA MODULE is a namespace for organizing code:\n  defines module com.example.utilities\nEvery .ek9 file declares exactly one module. Multiple files can share the same module name, and everything within the same module is automatically visible across those files. Modules control code organization, visibility, and the `references` mechanism for cross-module access.\n\nA PACKAGE is optional metadata for distribution:\n  defines package\n    version as Version: 1.0.0-0\n    description as String = \"...\"\n    deps <- { ... }\nA package block lives INSIDE a module and describes how to version, license, tag, and deploy that module as a distributable library. Only one file per module should contain a `defines package` block.\n\nKey differences:\n- Module: REQUIRED in every file. Organizes code into namespaces.\n- Package: OPTIONAL. Only needed when you want to distribute your code as a library.\n\nWhen you need a package:\n- You are publishing a library for others to depend on\n- You need version management (-IV, -SV, -PV)\n- You want to declare dependencies on other libraries\n- You want to deploy to a repository (-D)\n\nWhen you do NOT need a package:\n- You are writing an application (not a library)\n- Your code has no external dependencies\n- You are writing scripts or standalone programs\n\nThink of it this way:\n- Module = the code itself and its namespace\n- Package = the shipping label on that code\n\nRelated questions for package specifics:\n- How do I manage dependencies? (Q7)\n- How do I tag my package for discovery? (Q8)\n- How do I set a license? (Q9)\n- How do I separate dev vs production dependencies? (Q10)\n- How do I exclude transitive dependencies? (Q11)\n\nThe code example below shows a module that could work perfectly well without a package block. The package is only added when you decide to distribute it.\n\nSee Q6 for organizing modules. See Q2 for compile and run.","ek9Example":"defines module qa.getting.started.package.vs.module\n\n  defines function\n    greet() as pure\n      -> name as String\n      <- message as String: `Hello, ${name}!`\n\n  defines program\n    PackageVsModule()\n      stdout <- Stdout()\n\n      //This module works fine without a package block\n      //Add defines package only when you need to distribute it\n      stdout.println(greet(\"World\"))\n      stdout.println(\"This module has no package: it is a standalone program\")","migrationContext":"Java: package (namespace) vs Maven/Gradle project (distribution) are entirely separate. Python: module (namespace) vs PyPI package (distribution) are separate concepts with confusingly similar names. Rust: mod (namespace) vs crate/Cargo.toml (distribution). Go: package (namespace) vs module/go.mod (distribution). EK9 keeps both in the same source file but as distinct constructs.","keywords":["beginner","defines","deploy","difference","distribution","first","intro","library","module","namespace","organize","package","start","version"],"primaryTopics":[],"typicalErrors":[],"companions":[]}
{"id":14,"category":"Getting Started","question":"How do I see what dependencies my project has resolved?","url":"https://ek9.io/qa/QA0014.html","alternatePhrasings":["How do I list my resolved dependencies?","Is there a dependency tree command in EK9?","How do I check which dependency versions were selected?","How do I see rejected or excluded dependencies?","What is the EK9 equivalent of mvn dependency:tree or npm ls?","I use cargo tree to see dependencies, how do I do this in EK9?"],"answer":"Use `ek9 -Dp -v` to see your resolved dependencies:\n  ek9 -Dp -v app.ek9\n\nThe `-Dp` flag resolves all dependencies and `-v` (verbose) displays the results. The output shows two sections:\n\n1. Applied: dependencies that passed validation and will be used\n   Each shown as `moduleName-version`\n\n2. Not Applied: dependencies that were rejected, with reasons\n   Each shown as `moduleName-version(REASON)`\n\nRejection reasons:\n  MANUAL           you explicitly excluded it via excludeDeps\n  RATIONALISATION  a higher compatible version was selected instead\n  SAME_VERSION     duplicate entry, already included\n  OPTIMISED        parent dependency was rejected, so this is orphaned\n\nThe resolver also detects and reports:\n- Circular dependencies (build fails)\n- Semantic version breaches (different MAJOR versions, build fails)\n\nImportant: `-Dp` is not read-only. It cleans all build artifacts first, then re-resolves from scratch. Think of it as a fresh dependency lock, not just a query.\n\nThere is currently no read-only equivalent of Maven's `mvn dependency:tree` or npm's `npm ls`. The `-Dp -v` operation always performs the full clean-resolve cycle.\n\nRelated questions:\n- How do I manage dependencies? (Q7)\n- How do I exclude transitive dependencies? (Q11)\n- What is the difference between a package and a module? (Q13)\n\nSee Q7 for managing dependencies. See Q15 for semantic versioning.","ek9Example":"defines module qa.getting.started.list.resolved.deps\n\n  defines package\n\n    version as Version: 1.0.0-0\n    description as String = \"Example showing dependency resolution output\"\n    license <- \"MIT\"\n    publicAccess <- true\n\n    tags <- [\n      \"example\"\n    ]\n\n    applyStandardIncludes <- true\n\n    deps <- {\n      \"ekopen.network.support.utils\": \"1.6.1-9\",\n      \"ekopen.net.handy.tools\": \"3.2.1-0\"\n    }\n\n    excludeDeps <- {\n      \"ekopen.some.unwanted.pack\": \"ekopen.network.support.utils\"\n    }\n\n  defines program\n    ListResolvedDeps()\n      stdout <- Stdout()\n\n      stdout.println(\"Run: ek9 -Dp -v thisFile.ek9\")\n      stdout.println(\"To see applied and rejected dependencies\")","migrationContext":"Java: `mvn dependency:tree` (read-only). npm: `npm ls` (read-only). Rust: `cargo tree` (read-only). Go: `go list -m all` (read-only). pip: `pip list` or `pip freeze`. EK9: `ek9 -Dp -v` combines resolution with display but is not read-only — it cleans and re-resolves.","keywords":["accepted","beginner","cargo","circular","dependencies","first","intro","list","maven","migrate","mvn","npm","pip","rationalization","rejected","resolved","start","tree","verbose","version"],"primaryTopics":[],"typicalErrors":[],"companions":[]}
{"id":15,"category":"Getting Started","question":"What is semantic versioning and why must I use it in EK9 packages?","url":"https://ek9.io/qa/QA0015.html","alternatePhrasings":["Why does EK9 enforce semantic versioning?","How does version numbering work in EK9?","What do the version numbers mean in defines package?","I use Maven versions or npm version, how does EK9 handle versioning?","How do I version my EK9 package?"],"answer":"EK9 enforces semantic versioning (semver) for all packages. This is not optional: the compiler and dependency resolver rely on version numbers to make safe decisions about compatibility.\n\nVersion format: MAJOR.MINOR.PATCH-BUILD\n  version as Version: 2.1.3-0\n\nWhat each part means:\n  MAJOR  breaking API changes (consumers must update their code)\n  MINOR  new features, backwards compatible (safe to upgrade)\n  PATCH  bug fixes, backwards compatible (safe to upgrade)\n  BUILD  auto-incremented on every build attempt\n\nFeature branch format: MAJOR.MINOR.PATCH-FEATURE-BUILD\n  version as Version: 2.1.3-beta-5\n  Used for pre-release or experimental work.\n\nWhy EK9 ENFORCES this:\n- The dependency resolver auto-selects the highest compatible PATCH version. If two of your dependencies require the same library at 1.2.0-0 and 1.2.3-0, the resolver picks 1.2.3-0 because same MAJOR.MINOR means compatible.\n- Different MAJOR versions cause the build to FAIL. This protects you from silently using an incompatible API.\n- Without enforced semver, automatic version rationalization would be unsafe.\n\nCLI commands for version management:\n  ek9 -IV major app.ek9     increment major (resets minor, patch, build)\n  ek9 -IV minor app.ek9     increment minor (resets patch, build)\n  ek9 -IV patch app.ek9     increment patch (resets build)\n  ek9 -IV build app.ek9     increment build number only\n  ek9 -SV 2.0.0 app.ek9    set version explicitly (zeros build)\n  ek9 -SF 2.0.0-rc app.ek9  set feature version\n  ek9 -PV app.ek9           print current version\n\nCommon workflow:\n  1. Develop and test\n  2. ek9 -IV patch app.ek9   bump patch for bug fix release\n  3. ek9 -P app.ek9          package (uses -O3)\n  4. ek9 -D app.ek9          deploy\n\nRelated questions:\n- How do I manage dependencies? (Q7)\n- How do I see resolved dependencies? (Q14)\n\nSee Q7 for managing dependencies. See Q14 for listing resolved dependencies. See Q270 for supply chain security.","ek9Example":"defines module qa.getting.started.semantic.versioning\n\n  defines package\n\n    version as Version: 2.1.3-0\n    description as String = \"Demonstrates semantic versioning in EK9\"\n    license <- \"MIT\"\n    publicAccess <- true\n\n    tags <- [\n      \"example\",\n      \"versioning\"\n    ]\n\n    applyStandardIncludes <- true\n\n  defines program\n    SemanticVersioning()\n      stdout <- Stdout()\n\n      stdout.println(\"Package version: 2.1.3-0\")\n      stdout.println(\"MAJOR=2 MINOR=1 PATCH=3 BUILD=0\")\n      stdout.println(\"Run: ek9 -PV thisFile.ek9 to see current version\")\n      stdout.println(\"Run: ek9 -IV minor thisFile.ek9 to bump to 2.2.0-0\")","migrationContext":"Java: Maven versions are conventions not enforced (versions-maven-plugin for management). npm: semver by convention, npm version command. Rust: Cargo.toml version field, cargo semver-checks for enforcement. Go: module versions with /v2 path suffix for major. Python: PEP 440 versioning, not enforced. EK9 enforces semver at the compiler level: the dependency resolver depends on it for safe version rationalization.","keywords":["beginner","breaking","build","cargo","compatibility","first","increment","intro","major","maven","minor","mvn","npm","package","patch","semantic","semver","start","version","versioning"],"primaryTopics":[],"typicalErrors":[{"error":"E01040","correct":"defines program\n    SemanticVersioning()\n      stdout <- Stdout()","incorrect":"defines program\n    SemanticVersioning()\n      stdout <- Stdout()\n    SemanticVersioning()\n      stdout <- Stdout()","explanation":"Defining two constructs with the same name in the same module triggers E01040 — duplicate construct. Each program or function name must be unique. See ek9 -h E01040 for details."}],"companions":[]}
{"id":16,"category":"Getting Started","question":"How does indentation work in EK9?","url":"https://ek9.io/qa/QA0016.html","alternatePhrasings":["Does EK9 use braces or indentation?","How many spaces per indent level in EK9?","Can I use tabs in EK9?","Why does EK9 reject odd numbers of spaces?","How is EK9 indentation like Python?"],"answer":"EK9 uses significant indentation to define scope, similar to Python. There are no braces, no semicolons, and no end keywords.\n\nThe rules are strict and enforced by the lexer:\n\n1. Exactly 2 spaces per indentation level\n   Level 0: no indent (module declaration)\n   Level 1: 2 spaces (construct blocks like defines function)\n   Level 2: 4 spaces (function names, class names)\n   Level 3: 6 spaces (function body)\n   And so on.\n\n2. Tabs are NOT allowed\n   The lexer rejects tabs with: \"Tabs not supported for indentation; use spaces\"\n   Configure your editor to insert spaces when you press Tab.\n\n3. Odd numbers of spaces are NOT allowed\n   The lexer rejects odd space counts with: \"Odd number of spaces for indentation\"\n   Indentation must always be a multiple of 2. A line indented by 3 or 5 spaces is an error.\n\n4. Indentation must increase by exactly one level\n   You cannot jump from level 1 (2 spaces) to level 3 (6 spaces).\n   Each nested block increases by exactly 2 spaces.\n\n5. Dedenting can drop multiple levels at once\n   Returning from deeply nested code can decrease by several levels in one step.\n\nThe compiler generates synthetic INDENT and DEDENT tokens from the whitespace, similar to how Python's lexer works. This means the parser never sees raw spaces: it sees structured block markers.\n\nEditor setup:\n- Set tab key to insert 2 spaces (not a tab character)\n- Enable \"show whitespace\" to catch mixed indentation\n- The EK9 VSCode extension handles this automatically\n- In the REPL, pressing Tab inserts 2 spaces\n\nThe code example below shows correct indentation at multiple nesting levels.\n\nSee Q5 for source file structure. See Q49 for function definitions that demonstrate indentation levels. See Q622 for automatic code formatting.","ek9Example":"defines module qa.getting.started.indentation\n\n  defines function\n\n    outerFunction()\n      -> innerValue as Integer\n      <- rtn as String: String()\n\n      if innerValue > 0\n        rtn: \"positive: \" + $innerValue\n      else\n        rtn: \"non-positive\"\n\n  defines class\n\n    IndentExample\n      name as String: \"default\"\n\n      IndentExample()\n        -> n as String\n        name :=: n\n\n      describe()\n        <- rtn as String: `IndentExample(${name})`\n\n      default operator ?\n\n  defines program\n    Indentation()\n      stdout <- Stdout()\n\n      example <- IndentExample(\"two spaces per level\")\n      stdout.println(example.describe())\n      stdout.println(outerFunction(42))","migrationContext":"Python: also uses significant indentation but allows 4 spaces (PEP 8) or any consistent count. Java/C/Rust/Go: use braces for scope. Ruby: uses end keywords. Haskell: uses layout rules similar to EK9. EK9 is stricter than Python: exactly 2 spaces, no tabs, no odd counts.","keywords":["beginner","braces","dedent","first","indent","indentation","intro","level","migrate","nesting","scope","spaces","start","tabs","whitespace"],"primaryTopics":["indentation","whitespace","formatting"],"typicalErrors":[{"error":"E08180","correct":"name as String: \"default\"","incorrect":"name as String","explanation":"Class fields must be initialised inline. An uninitialised field triggers E08180. Provide a default value using a colon and literal or constructor call. See ek9 -h E08180 for details."},{"error":"E07520","correct":"default operator ?","incorrect":"operator ?","explanation":"The ? operator is inherited from the base type. Use 'default operator ?' to auto-generate it, or 'override operator ?' to provide a custom implementation. Declaring bare 'operator ?' triggers E07520 because operator semantics require a Boolean return. See ek9 -h E07520 for details."}],"companions":[]}
{"id":17,"category":"Getting Started","question":"Where is the main function in EK9?","url":"https://ek9.io/qa/QA0017.html","alternatePhrasings":["What is the entry point in EK9?","How do I define a program in EK9?","Can I have multiple programs in one file?","How does defines program work?","How do I pass arguments to a program?","How do I accept command-line parameters?"],"answer":"EK9 does not have a main() function. Instead, you define named programs inside a defines program block.\n\nA program is a named method inside defines program:\n\n  defines program\n    MyProgram()\n      stdout <- Stdout()\n      stdout.println(\"Hello\")\n\nKey points:\n\n1. Programs are NAMED, not anonymous main functions\n   You choose the name: HelloWorld(), DataProcessor(), WebServer(). This is more descriptive than a generic main() entry point.\n\n2. Multiple programs can exist in one file\n   Related utilities can live together in the same module. Run a specific one with: ek9 -r ProgramName file.ek9. If a file has only one program, -r is optional.\n\n3. Programs can accept typed command-line arguments\n   Declare parameters with -> and EK9 automatically converts string arguments to the declared types (String, Integer, Float, Boolean, Date, Time, and more). Exit code 9 means wrong argument count. Exit code 10 means type conversion failed.\n\n4. Programs share module resources\n   All programs in a module can access the module's functions, classes, constants, and types.\n\nThe code example below shows two programs in one module: a simple greeting and one that accepts a name argument.\n\nSee Q1 for a minimal program. See Q5 for source file structure. See Q49 for defining functions that programs can call. See Q93 for defining classes. See Q111 for components.","ek9Example":"defines module qa.getting.started.entry.point\n\n  defines function\n\n    greetingMessage()\n      <- message as String: \"Welcome to EK9\"\n\n  defines program\n\n    SimpleGreeting()\n      stdout <- Stdout()\n      stdout.println(greetingMessage())\n\n    PersonalGreeting()\n      -> name as String\n      stdout <- Stdout()\n      stdout.println(`Hello, ${name}!`)","migrationContext":"C: main(), Java: public static void main(String[] args), Go: func main() in package main, Rust: fn main(), Python: if __name__ == '__main__'. EK9 uses named programs in defines program blocks. Unlike Java and Go, multiple entry points per file. Unlike C and Rust, programs are named and selectable at runtime.","keywords":["arguments","beginner","defines","entry","first","function","intro","main","multiple","parameters","point","program","run","start"],"primaryTopics":["entry point","main function","program entry"],"typicalErrors":[{"error":"E50001","correct":"stdout.println(greetingMessage())","incorrect":"stdout.println(greetingMessge())","explanation":"A typo in the function name 'greetingMessge' means the compiler cannot find any identifier with that name. Check spelling carefully as EK9 is case-sensitive. See ek9 -h E50001 for details."}],"companions":[]}
{"id":18,"category":"Getting Started","question":"How do I add comments to my code?","url":"https://ek9.io/qa/QA0018.html","alternatePhrasings":["What comment styles does EK9 support?","Does EK9 have block comments?","What are documentation comments in EK9?","Does EK9 support doc comments like Javadoc?"],"answer":"EK9 supports four comment styles:\n\n1. Single-line comments: //\n   The most common style. Everything after // until end of line is ignored.\n   Example: // This is a comment\n\n2. Documentation comments: opened with <?- and closed with its mirror-image marker\n   Used for file headers, JSON metadata, and documentation that tools can extract. Can span multiple lines. IDEs and documentation generators recognise these as extractable documentation. The metadata headers in these QA example files use this style.\n\n3. General block comments: <!- ... -!>\n   Used for internal implementation notes NOT intended for documentation extraction. Can span multiple lines.\n\n4. HTML-style block comments: <!-- ... -->\n   An alternative to general block comments for developers familiar with HTML syntax. Functionally identical to the exclamation-mark style.\n\nAll comment types are processed by the lexer and completely removed before parsing. The parser never sees comment content.\n\nEK9 does NOT support C-style /* */ comments. Use one of the four styles above instead.\n\nBest practices:\n- Use // for quick inline notes\n- Use documentation comments for type and function documentation\n- Use block comments for temporarily disabling code or internal notes\n\nSee Q5 for source file structure where comments fit in the file layout.","ek9Example":"defines module qa.getting.started.comments\n\n  <?- Documentation comment: describes this function block -?>\n  defines function\n\n    <!- General block comment: internal implementation note -!>\n    describeComments()\n      <- result as String: \"comments example\"\n      // Single-line comment: the most common style\n      result: \"EK9 has four comment styles\"\n\n  <!-- HTML-style block comment: alternative syntax -->\n  defines program\n    Comments()\n      stdout <- Stdout()\n      stdout.println(describeComments())","migrationContext":"Java: // and /** */ Javadoc, Python: # and triple-quote docstrings, Rust: // and /// doc comments, Go: // and godoc conventions. EK9: four distinct comment styles with semantic meaning. Documentation comments are distinct from general comments, enabling tooling to extract only documentation.","keywords":["beginner","block","comment","comments","doc","docstring","documentation","first","inline","intro","javadoc","migrate","start"],"primaryTopics":["comments","code comments"],"typicalErrors":[{"error":"E50060","correct":"stdout.println(describeComments())","incorrect":"stdout.writeLine(describeComments())","explanation":"EK9 Stdout has println() not writeLine(). AI trained on other languages generates wrong method names. Triggers E50060 — method not resolved. See ek9 -h Stdout for the full API."}],"companions":[]}
{"id":19,"category":"Getting Started","question":"Can I use EK9 for scripting?","url":"https://ek9.io/qa/QA0019.html","alternatePhrasings":["Can I run EK9 files as scripts?","How do I pass command-line arguments to an EK9 program?","What does the shebang line do in EK9?","How do I pass parameters to a program in EK9?","How do I get input from the command line?","How do I accept user input arguments?"],"answer":"EK9 supports script-like usage with a compile-and-run model.\n\nEvery EK9 file starts with #!ek9 as the first line. This shebang line follows Unix conventions for script identification.\n\nRunning EK9 programs:\n  ek9 script.ek9                    Run the program\n  ek9 script.ek9 arg1 arg2          Pass arguments\n  ek9 -r SpecificProgram script.ek9 Run a named program\n\nProgram arguments are typed. Declare parameters and EK9 automatically converts command-line strings:\n\n  defines program\n    ProcessFile()\n      -> filename as String\n      ...\n\n  Run with: ek9 script.ek9 mydata.txt\n\nSupported argument types include String, Integer, Float, Boolean, Date, Time, DateTime, Duration, and more. EK9 handles the string-to-type conversion automatically.\n\nEK9 compiles and runs in a single step by default. There is no separate interpreter mode. The compiler caches compiled output so subsequent runs are fast.\n\nDirect execution as a script:\n  chmod u+x script.ek9\n  ./script.ek9 arg1 arg2\n\nBecause every .ek9 file starts with the #!ek9 shebang, making it executable with chmod lets you run it directly like a Bash or Python script. EK9 will compile and run it automatically.\n\nMultiple related programs can live in one file, making it easy to group CLI utilities together.\n\nSee Q1 for a minimal program. See Q2 for compile and run commands. See Q17 for entry points and named programs.","ek9Example":"defines module qa.getting.started.scripting\n\n  defines program\n\n    Greeting()\n      -> name as String\n      stdout <- Stdout()\n      stdout.println(`Hello, ${name}!`)\n\n    AddNumbers()\n      ->\n        a as Integer\n        b as Integer\n      stdout <- Stdout()\n      result <- a + b\n      stdout.println(`Sum: ${result}`)","migrationContext":"Python: direct script execution with shebang, Bash: shell scripts, Ruby: ruby script.rb, Go: go run main.go. EK9: ek9 script.ek9 with typed arguments and automatic conversion. Unlike Python and Bash, EK9 compiles before running. Unlike Go, multiple programs per file.","keywords":["argc","arguments","argv","beginner","cli","command-line","execute","first","input","intro","migrate","parameters","pass","program","run","script","scripting","shebang","start"],"primaryTopics":["scripting","command line arguments","program arguments","pass parameters","argv","program input","typed arguments"],"typicalErrors":[{"error":"E01072","correct":"stdout.println(`Hello, ${name}!`)","incorrect":"return `Hello, ${name}!`","explanation":"EK9 does not have a return statement. It was designed out of existence. Use return value declarations (<- rtn as Type) instead. Triggers E01072 — excluded keyword used. See ek9 -h E01072 for details."}],"companions":[]}
{"id":20,"category":"Getting Started","question":"What file extension does EK9 use?","url":"https://ek9.io/qa/QA0020.html","alternatePhrasings":["What is the file extension for EK9 source code?","Do EK9 files use .ek9 extension?","Does the file name need to match the module name?"],"answer":"EK9 source files use the .ek9 extension.\n\nKey points about EK9 files:\n\n1. Extension: .ek9\n   All EK9 source files must end with .ek9. The compiler will not process files with other extensions.\n\n2. File name does NOT need to match the module name\n   Unlike Java (where MyClass.java must contain class MyClass), EK9 file names are independent of module names. You can name files descriptively: utilities.ek9, web_server.ek9, data_model.ek9.\n\n3. One module per file\n   Each .ek9 file contains exactly one defines module declaration. This keeps the mapping between files and modules clear.\n\n4. File structure\n   Every file starts with #!ek9, followed by optional comments or metadata headers, then defines module, then construct blocks (defines function, defines class, defines program, etc.).\n\n5. Convention\n   Use lowercase names with underscores for descriptive file names. Group related files in directories that mirror your module hierarchy.\n\nSee Q5 for source file structure. See Q6 for organizing modules across files.","ek9Example":"defines module qa.getting.started.file.extension\n\n  defines function\n\n    fileInfo()\n      <- info as String: \"This file is named QA0020_file_extension.ek9\"\n\n  defines program\n    FileExtension()\n      stdout <- Stdout()\n      stdout.println(fileInfo())","migrationContext":"Java: .java with class name matching file name, Python: .py with no naming constraint, Rust: .rs with mod.rs convention, Go: .go with package declaration. EK9: .ek9 with no file-to-module name constraint. Simpler than Java's strict naming rule, similar flexibility to Python and Rust.","keywords":["beginner","convention","ek9","extension","file","first","intro","migrate","module","naming","source","start","structure"],"primaryTopics":["file extension","ek9 file"],"typicalErrors":[{"error":"E50060","correct":"stdout.println(fileInfo())","incorrect":"stdout.write(fileInfo())","explanation":"EK9 Stdout has println() for line output, not write(). Triggers E50060 — method not resolved. See ek9 -h Stdout for the full API."}],"companions":[]}
{"id":21,"category":"Getting Started","question":"How do I get help on a specific EK9 type or keyword?","url":"https://ek9.io/qa/QA0021.html","alternatePhrasings":["How do I look up EK9 type information from the command line?","What does ek9 -h do?","How do I list all help topics in EK9?","Can EK9 explain error codes?"],"answer":"EK9 has a comprehensive built-in help system accessible from the command line.\n\nCore commands:\n  ek9 -h <keyword>    Show help for a specific topic\n  ek9 -H              List ALL available help keywords\n  ek9 -h quickstart   Show a getting-started guide with Hello World\n\nWhat you can look up:\n\n1. Built-in types: ek9 -h String, ek9 -h List, ek9 -h Dict, ek9 -h Optional\n   Shows constructors, methods, operators, and usage examples.\n\n2. Language constructs: ek9 -h class, ek9 -h function, ek9 -h trait\n   Explains the construct with syntax guidance.\n\n3. Operators: ek9 -h :=, ek9 -h ?, ek9 -h $\n   Describes what each operator does and when to use it.\n\n4. Error codes: ek9 -h E05030\n   Explains what the error means and how to fix it.\n\n5. Concepts: ek9 -h lambda, ek9 -h generic, ek9 -h stream\n   Bridges concepts from other languages to EK9 equivalents.\n\nFuzzy matching:\n   The help system tolerates typos. If you type ek9 -h Strng, it resolves to String and shows the correct help.\n\nQ&A system:\n  ek9 -q <words>      Search for answers using natural language\n  ek9 -q              List all Q&A entries by category\n\nError detail levels:\n  -E0 (default) compact errors, -E1 visual with snippets, -E2 with suggestions, -E3 full explanations.\n\nSee Q23 for the full catalog of built-in types you can look up. See Q2 for all CLI command options.","ek9Example":"defines module qa.getting.started.help.system\n\n  defines program\n    HelpDemo()\n      stdout <- Stdout()\n      stdout.println(\"Use 'ek9 -h String' to see String's full API\")\n      stdout.println(\"Use 'ek9 -H' to list all help topics\")\n      stdout.println(\"Use 'ek9 -q' to search the Q&A knowledge base\")","migrationContext":"Java: no built-in CLI help for types, Rust: rustup doc or doc.rust-lang.org, Go: go doc package, Python: help() in REPL. EK9: ek9 -h for types, keywords, operators, error codes, and concepts all from one command. Unique features: fuzzy matching tolerates typos, and -q provides natural language Q&A search.","keywords":["api","beginner","code","documentation","error","first","help","intro","javadoc","keyword","lookup","reference","search","start","system","type"],"primaryTopics":[],"typicalErrors":[{"error":"E50060","correct":"stdout.println(\"Use 'ek9 -h String' to see String's full API\")","incorrect":"stdout.writeln(\"Use 'ek9 -h String' to see String's full API\")","explanation":"EK9 Stdout uses println() for output with newline. Methods like writeln() or write() do not exist. Triggers E50060 — method not resolved. See ek9 -h Stdout for the full API."}],"companions":[]}
{"id":22,"category":"Getting Started","question":"How do I declare a variable in EK9?","url":"https://ek9.io/qa/QA0022.html","alternatePhrasings":["What are the different ways to declare variables in EK9?","How does type inference work for local variables?","What modifiers does EK9 support?","What is the difference between <- and := and :=? operators?"],"answer":"EK9 provides several ways to declare variables and uses modifiers to control visibility, purity, and extensibility.\n\nVARIABLE DECLARATIONS\n\n1. Type inference with <- (idiomatic)\n  name <- \"Steve\"           Inferred as String\n  count <- 42                Inferred as Integer\n\n2. Explicit type\n  name as String: \"Steve\"\nUse when declaring base types for polymorphism.\n\n3. Reassignment operators\n  name <- \"Steve\"       Declaration (new variable)\n  name := \"John\"        Reassignment (existing variable)\n  name :=? \"Default\"    Guarded: only if currently unset\n\n4. Class/record fields\n  greeting as String: \"Hello\"    Private by default\n\n5. Parameters and returns (explicit types required)\n  formatName() as pure\n    -> first as String\n    <- result as String: first\n\n6. Named parameters\n  result <- formatName(first: \"Steve\", last: \"Limb\")\nIf ONE is named, ALL must be named, in declared order. Essential for dynamic functions:\n  multiplier <- (factor: count) is abstractOp as pure function\n    result :=? operand * factor\n\nMODIFIERS\n  public/protected/private   Access (default: package-private)\n  pure         No side effects, enforced on overrides\n  abstract     Must be overridden\n  override     Required when replacing parent method\n  open         Allows extension (closed by default)\n  default      Auto-generates operators\n  dispatcher   Multi-method dispatch by argument type\n  sanitized    Input sanitization at call site\n\nNaming: generic names ('value', 'data', 'item') rejected (E11031).\n\nSee Q49 for modifiers on functions. See Q93 for classes. See Q97 for records. See Q215 for sanitized. See Q28 for multiple variables. See Q127 for type inference. See Q290 for banned names. See Q292 for naming conventions.","ek9Example":"defines module qa.variables.declarations\n\n  defines function\n\n    formatName() as pure\n      ->\n        first as String\n        last as String\n      <- result as String: `${first} ${last}`\n\n    abstractOp() as pure abstract\n      -> operand as Integer\n      <- result as Integer?\n\n  defines class\n\n    Greeter\n      greeting as String: \"Hello\"\n\n      Greeter()\n        -> g as String\n        greeting :=: g\n\n      greet()\n        -> name as String\n        <- message as String: `${greeting}, ${name}`\n\n      default operator ?\n\n  defines program\n    Declarations()\n      stdout <- Stdout()\n\n      // Type inference - idiomatic for local variables\n      name <- \"Steve\"\n      count <- 42\n\n      // Explicit type - useful when declaring a base type\n      label as String: \"Count\"\n\n      // Reassignment\n      name := \"John\"\n\n      // Using the explicitly typed variable\n      stdout.println(`${label}: ${count}`)\n\n      // Named parameters at call site\n      full <- formatName(first: name, last: \"Limb\")\n      stdout.println(full)\n\n      // Dynamic function with named captures\n      multiplier <- (factor: count) is abstractOp as pure function\n        result :=? operand * factor\n\n      stdout.println($multiplier(3))\n\n      greeter <- Greeter(\"Welcome\")\n      stdout.println(greeter.greet(name))","migrationContext":"Java: Type name = value with all classes open by default, Python: name = value with duck typing, Rust: let name: Type = value with immutability by default, Go: var name Type = value or name := value. EK9: <- for declaration with inference, explicit Type for API boundaries, := for reassignment, :=? for guarded assignment. Modifiers: Java has public/private/protected/abstract/final/static, EK9 replaces final with closed-by-default and has no static. EK9 adds pure, dispatcher, sanitized, and open.","keywords":["abstract","assignment","beginner","declaration","declare","default","dispatcher","first","guard","immutable","inference","intro","isset","migrate","modifier","named","null-safe","open","override","parameter","private","protected","public","pure","return","safe","sanitized","side-effect","start","variable","virtual"],"primaryTopics":["declare variable","variable declaration","create variable"],"typicalErrors":[{"error":"E08180","correct":"greeting as String: \"Hello\"","incorrect":"greeting as String","explanation":"Class fields must be initialised inline with a value. An uninitialised field triggers E08180. Use a colon followed by a literal or constructor call. See ek9 -h E08180 for details."},{"error":"E07520","correct":"default operator ?","incorrect":"operator ?","explanation":"The ? operator is inherited from the base type and must use 'default' to auto-generate or 'override' for a custom implementation. Declaring bare 'operator ?' triggers E07520 because operator semantics require a Boolean return. See ek9 -h E07520 for details."},{"error":"E50001","correct":"full <- formatName(first: name, last: \"Limb\")","incorrect":"full <- formatNam(first: name, last: \"Limb\")","explanation":"A typo in the function name 'formatNam' means the compiler cannot find any identifier with that name in scope. EK9 requires exact spelling for all identifiers. See ek9 -h E50001 for details."}],"companions":[]}
{"id":23,"category":"Getting Started","question":"What basic types does EK9 have?","url":"https://ek9.io/qa/QA0023.html","alternatePhrasings":["What types are built into EK9?","What generic types does EK9 provide?","Does EK9 have functional types like Function or Predicate?","What collection types does EK9 have?"],"answer":"EK9 has a rich set of built-in types organized into several categories.\n\nPRIMITIVES\n  String     Text values: \"Hello\"\n  Integer    Whole numbers: 42\n  Float      Decimal numbers: 3.14\n  Boolean    true or false\n  Character  Single character: 'A'\n  Bits       Bit manipulation\n  Void       No value (used in type signatures)\n\nRICH VALUE TYPES\nThese go far beyond what most languages provide as built-ins:\n  Date, Time, DateTime    Calendar and clock values\n  Duration, Millisecond   Time spans\n  Money                   Currency-aware: 9.99#USD, 100#GBP\n  Colour                  Colour values: #FF8800\n  Dimension               Physical measurements: 100cm, 5.2kg\n  Resolution              Display resolution\n  Regex                   Regular expressions\n  GUID                    Globally unique identifiers\n  HMAC                    Cryptographic message authentication\n  Version                 Semantic version numbers\n  Path                    Geometric paths\n  Locale                  Internationalization locale\n  JSON                    JSON data handling\n\nGENERIC COLLECTION TYPES\nParameterized with 'of' syntax:\n  List of T               Ordered collection: List() of String\n  Dict of (K, V)          Key-value mapping: Dict() of (String, Integer)\n  DictEntry of (K, V)     Single key-value pair\n  Optional of T           Value that may be absent\n  Result of (O, E)        Success or error outcome\n  Iterator of T           Lazy sequence traversal\n  PriorityQueue of T      Priority-ordered queue\n  MutexLock of T          Thread-safe wrapper\n\nGENERIC FUNCTION TYPES (Key Building Block)\nUnlike Java where functional types were an afterthought (java.util.function added in Java 8), EK9 builds these into the language as first-class constructs. There are 16 generic function types: 8 pure and 8 non-pure.\n\nPure function types (no side effects):\n  Function of (T, R)      Takes T, returns R\n  Consumer of T            Takes T, no return\n  Supplier of T            No input, returns T\n  Predicate of T           Takes T, returns Boolean\n  UnaryOperator of T       Takes T, returns T (same type)\n  BiFunction of (T, U, R)  Takes T and U, returns R\n  BiConsumer of (T, U)     Takes T and U, no return\n  BiPredicate of (T, U)    Takes T and U, returns Boolean\n  Comparator of T          Compares two T values, returns Integer\n\nNon-pure function types (can have side effects, mutate state, perform I/O):\n  Routine of (T, R)        Takes T, returns R\n  Acceptor of T            Takes T, no return\n  Producer of T            No input, returns T\n  Assessor of T            Takes T, returns Boolean\n  BiRoutine of (T, U, R)   Takes T and U, returns R\n  BiAcceptor of (T, U)     Takes T and U, no return\n  BiAssessor of (T, U)     Takes T and U, returns Boolean\n\nThe pure/non-pure split is a language-level design decision. Pure functions are guaranteed safe for caching, parallelism, and testing. Non-pure functions are for I/O, state mutation, and real-world interaction. The compiler enforces this distinction.\n\nI/O TYPES\n  Stdout, Stderr, Stdin    Console I/O\n  TextFile                 File reading and writing\n  FileSystem               File system operations\n  FileSystemPath           File and directory paths\n\nNETWORK TYPES\n  TCP                      TCP socket communication\n  UDP                      UDP datagram communication\n\nSYSTEM TYPES\n  OS                       Operating system information\n  EnvVars                  Environment variable access\n  Signals                  OS signal handling\n  SystemClock              System time source\n  GetOpt                   Command-line option parsing\n\nSECURITY TYPES\n  InputSanitizer           XSS, SQL injection, command injection protection\n  Exception                Error handling with exit codes\n\nAll generic types use the 'of' keyword for parameterization: List of String, Dict of (String, Integer), Function of (Integer, String). Built-in generic types are closed and cannot be extended. Use composition and delegation instead of inheritance.\n\nUse 'ek9 -h TypeName' to see the full API for any built-in type, including constructors, methods, and operators. For example: 'ek9 -h String', 'ek9 -h List', 'ek9 -h Function'. Use 'ek9 -H' to list all available help topics. See also Q21 (How do I get help on a specific EK9 type or keyword?) for full details on the help system. See Q54 for the pure/non-pure distinction between Consumer and Acceptor, and the complete set of built-in abstract function types. See Q99 for creating enumerations. See Q140 for declaring built-in types as constants. See Q24 for type conversion. See Q26 for no primitives.","ek9Example":"defines module qa.basic.types\n\n  defines class\n\n    CheckExtension extends Check\n\n    Check as open\n      default Check()\n\n      override operator ? as pure\n        <- rtn <- true\n\n  defines function\n\n    doubleIt() as pure\n      -> operand as Integer\n      <- result as Integer: operand * 2\n\n  defines program\n    BasicTypes()\n      stdout <- Stdout()\n\n      // Primitives\n      greeting <- \"Hello\"\n      count <- 42\n      ratio <- 3.14\n      isReady <- true\n      letter <- 'A'\n\n      // Rich value types\n      today <- Date()\n      amount <- 9.99#USD\n      shade <- #FF8800\n      width <- 100cm\n\n      // Generic collection\n      names <- List() of String\n      names += \"Alice\"\n      names += \"Bob\"\n\n      // Generic function type - pure transformation\n      transform <- doubleIt\n\n      stdout.println(`${greeting} ${count} ${ratio}`)\n      stdout.println(`Ready: ${isReady} Letter: ${letter}`)\n      stdout.println(`Amount: ${amount}`)\n      stdout.println(`Colour: ${shade}`)\n      stdout.println(`Width: ${width}`)\n      stdout.println(`Today: ${today}`)\n      stdout.println(`Names: ${names}`)\n      stdout.println(`Doubled: ${transform(21)}`)\n\n      // Predicate - pure boolean test (built-in param names: t, r)\n      isPositive <- (threshold: 0) is Predicate of Integer as pure function\n        r :=? t > threshold\n\n      if isPositive(count)\n        stdout.println(`${count} is positive`)","migrationContext":"Java: primitives + Object wrappers + java.util collections + java.util.function (added Java 8), Python: built-in types + typing module, Rust: primitives + std collections + Fn/FnMut/FnOnce traits, Go: primitives + slices + maps + func types. EK9: all types built-in with consistent operators. Unique: Money, Colour, Dimension as primitives. 16 generic function types with enforced pure/non-pure split. No afterthought functional types.","keywords":["all","available","beginner","built-in","categories","first","intro","migrate","overview","primitive","start","type","types","what"],"primaryTopics":["basic types","data types","built-in types"],"typicalErrors":[{"error":"E05030","correct":"CheckExtension extends Check","incorrect":"CheckExtension extends List of String","explanation":"Built-in generic types like List, Dict, and Optional are closed and cannot be extended. Use composition and delegation instead of inheritance. See ek9 -h E05030 for details."},{"error":"E50010","correct":"names <- List() of String","incorrect":"names <- Listt() of String","explanation":"A typo in the type name 'Listt' means the compiler cannot find any type with that name. EK9 type names are case-sensitive and must be spelled exactly. See ek9 -h E50010 for details."}],"companions":[]}
{"id":24,"category":"Getting Started","question":"How do I convert between types?","url":"https://ek9.io/qa/QA0024.html","alternatePhrasings":["How does type casting work in EK9?","What is the dispatcher pattern in EK9?","How do I handle different types without instanceof?","How does the promote operator work?"],"answer":"EK9 has NO casting, NO instanceof, and NO multi-catch blocks. These were deliberately excluded because they encourage fragile type-checking code that breaks when new types are added. Instead, EK9 provides three clean mechanisms for type conversion and type-specific processing.\n\nTYPE PROMOTION: THE #^ OPERATOR\nThe promote operator converts a value to a wider compatible type. This happens automatically when you assign to a variable of a wider type:\n  intValue <- 42\n  floatResult as Float: intValue\nThe compiler inserts a call to Integer's #^ operator, which returns a Float. Only ONE level of promotion is allowed (no chaining). Built-in promotions include:\n  Integer to Float (safe numeric widening)\n  Character to String (single char to text)\n  Date to DateTime (date to full timestamp)\n  Millisecond to Duration (time unit widening)\nYou can define #^ on your own types for custom promotions.\n\nSTRING CONVERSION: THE $ OPERATOR\nThe $ operator converts any value to its String representation:\n  count <- 42\n  countText <- $count\nEvery built-in type defines $ for readable output. Override it on your own types to control how they display. Used automatically in string interpolation: `Value is ${count}`\n\nCONSTRUCTOR CONVERSION\nExplicit conversion uses constructors that accept other types:\n  parsed <- Integer(\"99\")\n  precise <- Float(42)\nThis is the explicit, intentional conversion path when you know what you want.\n\nTHE DISPATCHER: TYPE-SPECIFIC PROCESSING (Key Mechanism)\nThe dispatcher is how EK9 replaces instanceof, type switches, visitor patterns, and multi-catch blocks. It is the fundamental mechanism for processing values based on their actual runtime type.\n\nDeclare one method 'as dispatcher' taking a base type (typically Any). Then define private overloads for each specific type:\n  describe() as dispatcher\n    -> mainValue as Any\n    <- rtn as String: \"Unknown type\"\n  private describe()\n    -> mainValue as Integer\n    <- rtn as String: \"Got an Integer\"\n  private describe()\n    -> mainValue as Float\n    <- rtn as String: \"Got a Float\"\n\nAt runtime, the actual type of the argument determines which overload executes. The Any handler acts as the default for unhandled types.\n\nDispatcher rules:\n  Exactly ONE method marked 'as dispatcher' (the entry point)\n  Overloads have the same name but different parameter types\n  1 or 2 parameters only (not 0, not 3+)\n  All overloads must match purity (all pure or all non-pure)\n  Adding a new type means adding a new overload, not modifying existing code\n\nDISPATCHER FOR EXCEPTION HANDLING\nEK9 has only ONE catch block per try. There are no multi-catch blocks like Java's catch(IOException | SQLException e). Instead, use a dispatcher:\n  try\n    riskyOperation()\n  catch\n    -> ex as Exception\n    handleError(ex)\n  private handleError() as dispatcher\n    -> ex as Exception\n    <- rtn as String: \"General error\"\n  private handleError()\n    -> ex as AnException\n    <- rtn as String: \"Known error\"\n  private handleError()\n    -> ex as OtherException\n    <- rtn as String: \"Other error\"\n\nThis is fundamentally different from Java, Python, and C#. There is no escape hatch. You cannot check 'is this an X?' anywhere in EK9. You MUST use a dispatcher or the promotion operator.\n\nWHY NO INSTANCEOF OR CASTING? (Design Philosophy)\nNeeding to know the exact type of an object, or needing to cast it, is a code smell. It means you are not designing polymorphically. If your code says 'if this is a Dog, do X; if this is a Cat, do Y', you have pushed type-specific logic into the caller instead of the type itself. This violates the Open-Closed Principle: every new type forces changes to existing code.\n\nIn 30+ years of production systems, wild casts and type assumptions are a major source of defects. Java's ClassCastException, Python's AttributeError after isinstance checks, C++'s undefined behavior from bad casts, all stem from the same root problem: code that should not care about concrete types is forced to care.\n\nEK9 eliminates this entire class of defects by design. The dispatcher forces type-specific logic into the receiver (the overloaded methods), not the caller. Adding a new type means adding a new overload. Existing code is untouched. Some developers coming from Java or Python will find this very hard at first because it requires genuine polymorphic thinking, but it produces fundamentally better designs.\n\nWhy this matters:\n  No forgotten-case bugs when a new type is added\n  No instanceof chains that grow with every new type\n  No ClassCastException or casting errors at runtime\n  Each type handler is a separate, testable method\n  The compiler validates dispatcher consistency at compile time\n  Forces genuine polymorphic design, not just syntax convenience\n\nSee also Q23 (What basic types does EK9 have?) for the full type catalog, and use 'ek9 -h TypeName' to see which operators each type supports.\n\nSee Q23 for basic types. See Q25 for promote operator. See Q242 for conversion and introspection operators ($, $$, #?, #^, #<, #>). See Q250 for fixing type mismatch errors. See Q254 for the Any type and dispatcher fallback. See Q255 for cost-based method resolution.","ek9Example":"defines module qa.type.conversion\n\n  defines class\n\n    AnException extends Exception\n\n      AnException()\n        -> reason as String\n        super(reason)\n\n      default operator ?\n\n    OtherException extends Exception\n\n      OtherException()\n        -> reason as String\n        super(reason)\n\n      default operator ?\n\n    TypeProcessor\n\n      describe() as dispatcher\n        -> mainValue as Any\n        <- rtn as String: \"Unknown type\"\n\n      private describe()\n        -> mainValue as Integer\n        <- rtn as String: `Integer: ${mainValue}`\n\n      private describe()\n        -> mainValue as Float\n        <- rtn as String: `Float: ${mainValue}`\n\n      private describe()\n        -> mainValue as String\n        <- rtn as String: `String: ${mainValue}`\n\n      private handleError() as dispatcher\n        -> ex as Exception\n        <- rtn as String: \"General error: \" + $ex\n\n      private handleError()\n        -> ex as AnException\n        <- rtn as String: \"Known error: \" + $ex\n\n      private handleError()\n        -> ex as OtherException\n        <- rtn as String: \"Other error: \" + $ex\n\n      processWithErrorHandling()\n        -> mainValue as Any\n        <- rtn as String: String()\n\n        stdout <- Stdout()\n        try\n          rtn: describe(mainValue)\n        catch\n          -> ex as Exception\n          rtn: handleError(ex)\n          stdout.println(rtn)\n\n  defines program\n    TypeConversionDemo()\n      stdout <- Stdout()\n\n      // Promotion: Integer to Float via #^ operator\n      intValue <- 42\n      floatResult as Float: intValue\n      stdout.println(`Promoted: ${floatResult}`)\n\n      // Promotion: Character to String\n      letter <- 'Z'\n      textResult as String: letter\n      stdout.println(`Promoted: ${textResult}`)\n\n      // String conversion with $ operator\n      count <- 100\n      countText <- $count\n      stdout.println(`String: ${countText}`)\n\n      // Constructor conversion\n      parsed <- Integer(\"99\")\n      stdout.println(`Parsed: ${parsed}`)\n\n      // Dispatcher: process different types\n      processor <- TypeProcessor()\n\n      items <- [1, 2.5, \"hello\"]\n      for listItem in items\n        description <- processor.describe(listItem)\n        stdout.println(description)\n\n      // Dispatcher for exception handling (single catch, dispatched)\n      result <- processor.processWithErrorHandling(42)\n      stdout.println(result)","migrationContext":"Java: casting with (Type)obj, instanceof with pattern matching (Java 21), multi-catch blocks, visitor pattern. Python: isinstance() checks, type() comparisons, multiple except clauses. Rust: as keyword, From/Into traits, match with pattern guards. Go: type assertions x.(Type), type switches. C#: is/as operators, pattern matching in switch. EK9: NO casting, NO instanceof, NO multi-catch. Uses #^ promotion, $ string conversion, constructor conversion, and dispatcher pattern. Dispatcher is mandatory for type-specific processing. Forces redesign of type handling from caller-side checking to receiver-side dispatch.","keywords":["any","beginner","cast","casting","catch","conversion","convert","dispatch","dispatcher","exception","first","handler","instanceof","intro","migrate","overload","promote","promotion","sealed","start","type","visitor"],"primaryTopics":["type conversion","cast","convert type"],"typicalErrors":[{"error":"E07520","correct":"default operator ?","incorrect":"operator ?","explanation":"The ? operator is inherited from the base type (Exception). Use 'default operator ?' to auto-generate it. Declaring bare 'operator ?' triggers E07520 because operator semantics require a Boolean return. See ek9 -h E07520 for details."}],"companions":[]}
{"id":25,"category":"Getting Started","question":"What is the promote operator?","url":"https://ek9.io/qa/QA0025.html","alternatePhrasings":["How does automatic type promotion work in EK9?","How does EK9 handle implicit type conversion?","Why does EK9 only allow one level of promotion?","How does the #^ operator work?"],"answer":"The promote operator (#^) enables controlled, single-level type widening. It is central to how EK9 resolves method signatures and assignments when types do not match exactly.\n\nHOW IT WORKS\nWhen you assign a value to a variable of a different type, or pass an argument to a function expecting a different type, the compiler checks if the source type has a #^ operator that returns the target type:\n  intValue <- 42\n  floatResult as Float: intValue\nThe compiler sees Integer assigned to Float, finds Integer's #^ operator returns Float, and automatically inserts the promotion call. The same applies to function calls:\n  acceptsFloat(21)\nThe Integer literal 21 is automatically promoted to Float because acceptsFloat expects a Float parameter.\n\nBUILT-IN PROMOTIONS\n  Integer to Float     Safe numeric widening\n  Character to String  Single character to text\n  Date to DateTime     Date to full timestamp\n  Millisecond to Duration  Time unit widening\n\nUSER-DEFINED PROMOTIONS\nYou can define #^ on your own types:\n  Measurement\n    operator #^ as pure\n      <- rtn as String: some formatted text\nA type can have only ONE #^ operator. This is by design.\n\nSINGLE PROMOTION ONLY (Critical Rule)\nThe compiler attempts exactly ONE promotion. It does NOT chain promotions. If Integer promotes to Float and Float promotes to something else, the compiler will NOT automatically go Integer to Float to that other type. It tries one step and stops.\n\nThis is enforced in the method matching mechanism. After promoting a type, the compiler checks if the promoted type is directly assignable to the target using uncoerced matching only. No further promotion is attempted.\n\nCOST-BASED METHOD MATCHING\nWhen resolving which method overload to call, the compiler assigns costs:\n  Exact type match:      0.0  (perfect, always preferred)\n  Superclass match:      0.05 (inheritance)\n  Trait match:           0.10 (interface implementation)\n  Promotion match:       0.5  (via #^ operator)\n  Any type match:        20.0 (universal fallback)\n  No match:             -1.0  (invalid)\n\nThe method with the lowest total cost wins. Exact matches are always preferred over promoted matches. If two methods score within 0.001 of each other, the compiler reports an ambiguity error rather than guessing.\n\nWHY OTHER LANGUAGES GET THIS WRONG\nImplicit conversion chains are one of the most prolific sources of subtle bugs across programming languages.\n\nC++: User-defined conversions can chain with standard conversions. A converting constructor plus an implicit operator can create paths the developer never intended. The 'explicit' keyword was added specifically to stop this, but developers must remember to use it.\n\nJavaScript: The == operator performs type coercion through multiple steps: [] == false is true, '0' == false is true, but [] == '0' is false. The entire '===' operator exists solely because == coercion is so unreliable.\n\nScala: Implicit conversions (implicit def) can compose into chains. Scala 2's implicit resolution was so complex that even experienced developers could not predict which conversion path the compiler would choose. Scala 3 replaced this with 'given' and 'using' specifically because chained implicits were too dangerous.\n\nJava: Autoboxing combined with widening produces surprises. Integer == Long compiles but compares object identity, not values. Ternary expressions with mixed numeric types silently widen in unexpected directions.\n\nPython: While mostly explicit, __add__ returning NotImplemented triggers __radd__ on the other operand, which can create surprising dispatch chains with mixed types.\n\nEK9's single-promotion rule eliminates all of these problems. One step, predictable, cost-ranked, compiler-verified. If you need more complex conversion, use an explicit constructor call. The compiler will never silently chain conversions behind your back.\n\nSee also Q24 (How do I convert between types?) for the full type conversion story including dispatcher and constructor conversion.\n\nSee Q24 for type conversion. See Q39 for integer and float. See Q242 for all conversion and introspection operators. See Q258 for type coercion and promotion in method calls. See Q552 for Date-to-DateTime promotion.","ek9Example":"defines module qa.promote.operator\n\n  defines function\n\n    acceptsFloat() as pure\n      -> mainValue as Float\n      <- rtn as Float: mainValue * 2.0\n\n  defines class\n\n    Measurement\n      reading <- Float()\n\n      Measurement()\n        -> reading as Float\n        this.reading :=: reading\n\n      reading() as pure\n        <- rtn as Float: reading\n\n      operator #^ as pure\n        <- rtn as String: `${reading}units`\n\n      default operator ?\n\n  defines program\n    PromoteDemo()\n      stdout <- Stdout()\n\n      // Automatic promotion: Integer to Float in assignment\n      intValue <- 42\n      floatResult as Float: intValue\n      stdout.println(`Promoted int to float: ${floatResult}`)\n\n      // Automatic promotion: Integer to Float in function call\n      doubled <- acceptsFloat(21)\n      stdout.println(`Promoted in call: ${doubled}`)\n\n      // Character to String promotion\n      letter <- 'X'\n      textValue as String: letter\n      stdout.println(`Promoted char to string: ${textValue}`)\n\n      // User-defined promotion\n      sensor <- Measurement(98.6)\n      displayText as String: sensor\n      stdout.println(`Custom promote: ${displayText}`)","migrationContext":"C++: implicit conversions chain (constructor + operator), 'explicit' keyword added to fix. JavaScript: == coercion chains create bizarre equality. Scala 2: implicit def chains unpredictable, replaced in Scala 3. Java: autoboxing + widening surprises (Integer == Long). Python: __add__/NotImplemented/__radd__ chains. EK9: single #^ promotion only, cost-based matching (0.0 exact, 0.05 super, 0.10 trait, 0.5 promotion), no chaining. Predictable, verifiable, safe.","keywords":["beginner","chain","coerce","coercion","convert","cost","first","implicit","intro","matching","migrate","operator","overload","promote","promotion","resolution","signature","single","start","widening"],"primaryTopics":[],"typicalErrors":[{"error":"E08180","correct":"reading <- Float()","incorrect":"reading as Float","explanation":"Class fields must be initialised inline. An uninitialised field triggers E08180. Use a constructor call like Float() or a literal value. See ek9 -h E08180 for details."},{"error":"E07520","correct":"default operator ?","incorrect":"operator ?","explanation":"The ? operator is inherited from the base type and requires 'default' or 'override'. Declaring bare 'operator ?' triggers E07520 because operator semantics require a Boolean return. See ek9 -h E07520 for details."}],"companions":[]}
{"id":26,"category":"Getting Started","question":"Why does EK9 not have primitives?","url":"https://ek9.io/qa/QA0026.html","alternatePhrasings":["How do I use primitives instead of objects in EK9?","Does EK9 have autoboxing?","Are Integer and Float objects or primitives in EK9?","Why is everything an object in EK9?"],"answer":"EK9 has no primitives. Every value is an object. There is no autoboxing, no unboxing, no dual representation. Integer is an object. Float is an object. Boolean is an object. Character is an object. Always.\n\nYou cannot use primitives instead of objects because primitives do not exist in EK9. This is a deliberate design decision that eliminates an entire class of bugs and inconsistencies found in other languages.\n\nWHY OTHER LANGUAGES HAVE PRIMITIVES (AND THE PROBLEMS THEY CAUSE)\n\nJava has two type systems that do not mix cleanly. int is a primitive, Integer is an object. This creates:\n  Autoboxing surprises: Integer a = 127; Integer b = 127; a == b is true. Integer a = 128; Integer b = 128; a == b is false. The JVM caches small Integer objects but not large ones, so == works for small values and fails for large ones. This has caused production bugs in banking and financial systems.\n  Null pointer exceptions: Integer x = null; int y = x; throws NullPointerException at the unboxing. Primitives cannot be null but their boxed counterparts can, creating a mismatch that the type system cannot prevent.\n  Collections cannot hold primitives: List<int> is illegal, you must use List<Integer>. This forces autoboxing on every add and unboxing on every get, with performance and correctness implications.\n  Two equality systems: == compares identity for objects but value for primitives. The same operator means different things depending on whether autoboxing occurred.\n\nC# has value types (struct) versus reference types (class). While more consistent than Java, it still has boxing when value types are stored in object references, and subtle differences in equality and copying semantics.\n\nC++ has primitive types that do not participate in the type hierarchy. You cannot put an int in a container of polymorphic objects without wrapping it. Templates help but create code bloat.\n\nGo has no classes and no inheritance, so the primitive/object distinction is less painful, but you still cannot define methods on built-in types or treat int and string polymorphically.\n\nEK9'S UNIFIED OBJECT MODEL\n\nIn EK9, every value is an object with:\n  Operators: ==, <>, <, >, <=, >=, <=>, $, #?, ? all work on every type\n  Methods: every type has a consistent API discoverable via 'ek9 -h TypeName'\n  Tri-state semantics: every object can be set, unset, or absent (see Q29)\n  Polymorphism: Integer, Float, String can all be stored in collections, passed as Any, dispatched on\n\nThere is no == that sometimes compares values and sometimes compares identity. There is no autoboxing that silently converts between representations. There is no NullPointerException from unboxing a null wrapper.\n\n  count <- 42\n  ratio <- 3.14\n  ready <- true\n  letter <- 'A'\n\nAll four are objects. All four support the same operator patterns. All four can be put in a List of Any. All four have consistent tri-state behaviour with the ? operator.\n\nPERFORMANCE\nYou might ask: are objects slower than primitives? At the language level, EK9 optimises behind the scenes. The compiler and runtime are free to use the most efficient representation internally. What matters is that the developer never sees inconsistency. The unified model means fewer bugs, simpler reasoning, and no surprise behaviour from autoboxing or identity comparison.\n\nSee also Q23 (What basic types does EK9 have?) for the full type catalog. See Q93 for defining classes.\n\nSee Q23 for basic types. See Q7 for managing dependencies.","ek9Example":"defines module qa.no.primitives\n\n  defines program\n    NoPrimitivesDemo()\n      stdout <- Stdout()\n\n      // All values are objects - no primitives\n      count as Integer = 42\n      ratio <- 3.14\n      ready <- true\n      letter <- 'A'\n\n      // All support the same operator patterns\n      stdout.println(`Count is set: ${count?}`)\n      stdout.println(`Ratio is set: ${ratio?}`)\n      stdout.println(`Ready is set: ${ready?}`)\n      stdout.println(`Letter is set: ${letter?}`)\n\n      // All can go in a heterogeneous collection\n      items <- [count, ratio, ready, letter]\n      for listItem in items\n        stdout.println(`Item: ${listItem?}`)\n\n      // All support $ for string conversion\n      stdout.println(`${count} ${ratio} ${ready} ${letter}`)","migrationContext":"Java: int/Integer dual system, autoboxing caches 127, == means different things, NPE from unboxing null. C#: value types box when stored in object references, struct vs class semantics. C++: primitives outside type hierarchy, cannot be polymorphic. Go: no methods on built-in types. Python: everything is an object (closest to EK9). Ruby: everything is an object. Smalltalk: everything is an object. EK9: unified object model, no primitives, no autoboxing, no dual equality, consistent operators on all types.","keywords":["autoboxing","beginner","boxing","first","int","integer","intro","migrate","object","primitive","primitives","reference","start","unboxing","unified","value","wrapper"],"primaryTopics":["no primitives","everything is object","autoboxing"],"typicalErrors":[{"error":"E50010","correct":"count as Integer = 42","incorrect":"count as int = 42","explanation":"int, float, char, bool do not exist as primitives."}],"companions":[]}
{"id":27,"category":"Getting Started","question":"Is EK9 statically or dynamically typed?","url":"https://ek9.io/qa/QA0027.html","alternatePhrasings":["Does EK9 check types at compile time or runtime?","How does type inference work in EK9?","Do I have to declare types for every variable in EK9?","Is EK9 strongly typed?"],"answer":"EK9 is statically and strongly typed. All types are resolved at compile time. There are no runtime type errors from incorrect assignments, no 'undefined is not a function', no AttributeError because a method does not exist. If it compiles, the types are correct.\n\nBut EK9 does not feel like a statically typed language. Type inference means you rarely write type annotations for local variables:\n  count <- 42\n  name <- \"Steve\"\n  ratio <- 3.14\n  items <- List() of String\nThe compiler infers Integer, String, Float, and List of String. You get the safety of static typing with the brevity of dynamic typing.\n\nWHEN YOU MUST DECLARE TYPES\nType annotations are required in specific places where inference is not possible or where clarity matters:\n  Parameters: -> operand as Integer (always required)\n  Return values: <- result as String (always required)\n  Explicit base types: shape as Shape: Circle() (when you want the variable typed as the base)\n  Properties: name <- String() (class/record fields)\n\nWHEN YOU SHOULD NOT DECLARE TYPES\nFor local variables where the type is obvious from the right-hand side, let inference do the work:\n  count <- 42 (not count as Integer: 42)\n  name <- \"hello\" (not name as String: \"hello\")\n  today <- Date() (not today as Date: Date())\nDeclaring the type when inference can determine it is redundant and reduces readability.\n\nWHY NOT DYNAMIC TYPING?\nDynamic languages (Python, JavaScript, Ruby) defer type checking to runtime. This means:\n  Python: name = 42; name.upper() compiles fine but crashes at runtime with AttributeError\n  JavaScript: '5' - 3 gives 2 but '5' + 3 gives '53'. Type coercion rules are inconsistent and produce silent bugs.\n  Ruby: method_missing can intercept any call, making it impossible to know at edit time whether a method exists\n\nThese bugs are found in production, not during development. Every dynamic language eventually grows optional type systems (Python type hints, TypeScript, Sorbet for Ruby) because runtime type errors are too expensive in production.\n\nEK9 starts with static types so you never need to retrofit safety later. The compiler catches type mismatches, missing methods, incorrect operator usage, and incompatible assignments before your code ever runs.\n\nSTRONG TYPING\nEK9 is also strongly typed. There are no implicit conversions except the single-level promote operator (see Q25). You cannot accidentally treat a String as an Integer or a Boolean as a number. The type system enforces boundaries and the compiler reports violations as errors, not warnings.\n\nCOMPILE-TIME GUARANTEES\nBecause EK9 is statically typed, the compiler can guarantee:\n  Every method call resolves to an actual method\n  Every operator is defined on the type it is applied to\n  Every assignment is type-compatible (or has a valid promotion)\n  Every function parameter matches the expected type\n  Every return value matches the declared return type\n  Generic types are fully resolved and checked\n\nSee also Q22 (How do I declare a variable?) for type inference syntax and Q25 (What is the promote operator?) for the single automatic type widening mechanism.\n\nSee Q23 for basic types. See Q29 for unset variables. See Q257 for constrained types that restrict values at the type level.","ek9Example":"defines module qa.static.typing\n\n  defines function\n\n    addValues() as pure\n      ->\n        first as Integer\n        second as Integer\n      <- result as Integer: first + second\n\n  defines program\n    StaticTypingDemo()\n      stdout <- Stdout()\n\n      // Type inference - compiler knows the types\n      count <- 42\n      name <- \"Steve\"\n      ratio <- 3.14\n      items <- List() of String\n\n      items += \"one\"\n      items += \"two\"\n\n      // Explicit type when you want a base type\n      floatValue as Float: count\n\n      // All types checked at compile time\n      result <- addValues(count, 10)\n      stdout.println(`${name}: ${result}`)\n      stdout.println(`Ratio: ${ratio}`)\n      stdout.println(`Items: ${items}`)\n      stdout.println(`Float: ${floatValue}`)","migrationContext":"Python: dynamically typed, type hints optional (PEP 484), runtime AttributeError. JavaScript: dynamically typed, TypeScript added for safety, implicit coercion ('5'+3='53'). Ruby: dynamically typed, Sorbet added. Java: statically typed but verbose (var added Java 10). Kotlin: statically typed with inference. Rust: statically typed with inference. Go: statically typed with := inference. EK9: statically typed with <- inference, feels dynamic, catches everything at compile time.","keywords":["anonymous","beginner","capture","check","closure","compile","dynamic","first","infer","inference","intro","migrate","runtime","safety","start","static","strong","type","typed","typing"],"primaryTopics":["static typing","type system","strong typing"],"typicalErrors":[{"error":"E50060","correct":"result <- addValues(count, 10)","incorrect":"result <- count.addValues(10)","explanation":"EK9 resolves methods on the type they are defined on. Calling a standalone function as if it were a method on Integer would trigger method-not-found. See ek9 -h E50060 for details."}],"companions":[]}
{"id":28,"category":"Getting Started","question":"How do I declare multiple variables at once?","url":"https://ek9.io/qa/QA0028.html","alternatePhrasings":["Does EK9 have tuple unpacking or destructuring?","Can I declare several variables on one line in EK9?","How do I return multiple values from a function in EK9?"],"answer":"EK9 declares one variable per line. There is no multi-variable declaration, no tuple unpacking, and no destructuring. This is a deliberate design decision for clarity and readability.\n\nLOCAL VARIABLES: ONE PER LINE\n  count <- 42\n  name <- \"Steve\"\n  ratio <- 3.14\n  ready <- true\nEach variable gets its own line with its own <- declaration. There is no syntax for count, name, ratio <- 42, \"Steve\", 3.14 or anything similar.\n\nFUNCTION PARAMETERS: MULTIPLE BUT SEPARATE LINES\nWhen a function takes multiple parameters, each is declared on its own line in a -> block:\n  calculate()\n    ->\n      width as Float\n      height as Float\n    <- area as Float: width * height\nThe single-parameter shorthand puts it on the same line as ->:\n  square()\n    -> side as Float\n    <- area as Float: side * side\n\nMULTIPLE RETURN VALUES\nEK9 does not have tuple unpacking like Python's a, b = get_pair(). Instead, use a record or class to group related return values:\n  defines record\n    Outcome\n      message <- String()\n      exitCode <- Integer()\n      default operator ?\n  processData()\n    -> inputData as String\n    <- outcome as Outcome: Outcome()\nThe caller accesses fields by name: result.message, result.exitCode. This is more readable than positional tuple unpacking because every value has a name.\n\nWHY NO DESTRUCTURING?\nDestructuring and tuple unpacking introduce positional coupling. In Python, a, b, c = get_values() breaks silently if the function starts returning four values or if someone swaps the order. Named fields make the contract explicit and changes visible.\n\nOTHER LANGUAGES\n  Python: a, b = 1, 2 and a, b, c = get_triple() with positional unpacking\n  Go: var (a int; b string) blocks and a, b := getTwoValues() multi-return\n  Rust: let (a, b) = (1, 2) destructuring and pattern matching\n  JavaScript: const {a, b} = obj and const [x, y] = arr\n  Kotlin: val (a, b) = Pair(1, 2) with componentN() convention\n\nEK9 avoids all of these. One variable per line, named fields for grouped values, explicit and readable. The two extra lines you write are repaid every time someone reads the code.\n\nSee Q29 for unset variables. See Q22 for declaring variables.","ek9Example":"defines module qa.multiple.variables\n\n  defines record\n    Outcome\n      message <- String()\n      exitCode <- Integer()\n      default operator ?\n\n  defines function\n\n    processData()\n      -> inputData as String\n      <- outcome as Outcome: Outcome()\n      require inputData?\n\n  defines program\n    MultipleVariablesDemo()\n      stdout <- Stdout()\n\n      // One variable per line\n      count <- 42\n      name <- \"Steve\"\n      ratio <- 3.14\n      ready <- true\n\n      stdout.println(`${name}: ${count} ${ratio} ${ready}`)\n\n      // Grouped return values via record\n      result <- processData(\"test\")\n      stdout.println(`Message: ${result.message}`)\n      stdout.println(`Exit code: ${result.exitCode}`)","migrationContext":"Python: a, b = 1, 2 tuple unpacking, multiple return values. Go: var (...) blocks, a, b := f() multi-return. Rust: let (a, b) destructuring, pattern matching. JavaScript: const {a, b} = obj, const [x, y] = arr. Kotlin: val (a, b) = Pair with componentN(). EK9: one variable per line, no destructuring, no tuple unpacking. Use records for grouped return values with named fields.","keywords":["beginner","declare","destructuring","first","group","intro","many","migrate","multiple","record","return","several","start","tuple","unpacking","variable"],"primaryTopics":[],"typicalErrors":[{"error":"E08180","correct":"message <- String()","incorrect":"message as String","explanation":"Record fields must be initialised inline. An uninitialised field triggers E08180. Use a constructor call like String() for an unset default. See ek9 -h E08180 for details."},{"error":"E07520","correct":"default operator ?","incorrect":"operator ?","explanation":"The ? operator is inherited from the base type and requires 'default' for auto-generation or 'override' for custom implementation. Declaring bare 'operator ?' triggers E07520 because operator semantics require a Boolean return. See ek9 -h E07520 for details."}],"companions":[]}
{"id":29,"category":"Getting Started","question":"What is the default value of an unset variable?","url":"https://ek9.io/qa/QA0029.html","alternatePhrasings":["What happens when a variable has no value in EK9?","How does the tri-state object model work in EK9?","Does EK9 have null?","How do I check if a variable is set in EK9?"],"answer":"EK9 has no null. Every object exists in one of three states: absent, present but unset, or present and set. The ? operator checks whether an object is set.\n\nTHE THREE STATES\n  Absent: object does not exist (Dict lookup miss, empty Optional).\n  Present but unset: object exists, no meaningful value. String() is unset. ? returns false.\n  Present and set: object has valid value. String(\"hello\") is set. ? returns true.\n\nCHECKING STATE WITH ?\n  name <- String()\n  if name?\n    stdout.println(\"Has a value\")\n\nGuarded assignment :=? only assigns if unset:\n  name :=? \"default\"    assigns because unset\n  name :=? \"other\"      no-op because already set\n\nGUARDS IN CONTROL FLOW\n  if record <- database.find(key)\n    stdout.println(`Found: ${record}`)\nWorks identically in if, switch, for, while, and try.\n\nTYPE-SPECIFIC BEHAVIOUR\n  Primitives (String, Integer, Float, Boolean, Character): unset when created with no argument.\n  Collections (List, Dict): always set when created, even if empty.\n  Containers (Optional, DictEntry): depends on contained value.\n\nINVALID CONSTRUCTION CREATES UNSET\n  parsed <- Integer(\"not a number\")\n  if parsed?    false — invalid input produces unset, no exception\n\nBOOLEAN TRI-STATE\nBoolean() is unset (not yet known), distinct from Boolean(false).\n\nJSON MAPPING\n  Absent: field omitted. Unset: field as null. Set: field has value.\nCritical for REST PATCH distinguishing 'not provided' from 'clear value' from 'update'.\n\nWHY NOT NULL?\nNull is 'the billion-dollar mistake'. EK9's tri-state replaces it: objects always exist, ? checks for meaningful value, guards prevent processing unset values, compiler enforces handling.\n\nSee Q22 for :=? guarded assignment. See Q26 for unified object model. See Q30 for Boolean tri-state. See Q47 for Optional. See Q48 for Result. See Q104 for uninitialised properties. See Q141 for constants. See Q188 for JSON. See Q222 for enum unset state. See Q243 for coalescing operators. See Q251 for unset variable errors. See Q256 for Void type. See Q261 for idiomatic patterns. See Q269 for input validation. See Q277 for AI null checks vs guards. See Q286 for guarded assignment accumulation.","ek9Example":"defines module qa.unset.variables\n\n  defines program\n    UnsetVariablesDemo()\n      stdout <- Stdout()\n\n      // Present but unset\n      name <- String()\n      stdout.println(`Name is set: ${name?}`)\n\n      // Present and set\n      greeting <- \"Hello\"\n      stdout.println(`Greeting is set: ${greeting?}`)\n\n      // Guarded assignment - only assigns if unset\n      name :=? \"default\"\n      stdout.println(`Name after guard: ${name}`)\n\n      name :=? \"other\"\n      stdout.println(`Name after second guard: ${name}`)\n\n      // Guard in control flow\n      items <- List() of String\n      items += \"first\"\n\n      // Collections are always set when created\n      stdout.println(`Items is set: ${items?}`)\n\n      emptyList <- List() of Integer\n      stdout.println(`Empty list is set: ${emptyList?}`)\n\n      // Unset integer\n      count <- Integer()\n      stdout.println(`Unset count: ${count?}`)\n      count :=? 42\n      stdout.println(`Count after guard: ${count}`)","migrationContext":"Java: null/NullPointerException. Rust: Option<T>. Kotlin/Swift: nullable types. Python: None. JS: null AND undefined. EK9: tri-state (absent/unset/set), no null, ? operator, :=? guarded assignment, guards in control flow.","keywords":["absent","beginner","billion","default","first","intro","migrate","mistake","null","start","swift","tri-state","tristate","undefined","unset","value"],"primaryTopics":["unset variable","default value","null","uninitialized"],"typicalErrors":[],"companions":[]}
{"id":30,"category":"Getting Started","question":"How do I work with Boolean values?","url":"https://ek9.io/qa/QA0030.html","alternatePhrasings":["What logical operators does EK9 have?","Can a Boolean be unset in EK9?","How does Boolean tri-state work in EK9?"],"answer":"In most languages, a Boolean is either true or false. In EK9, a Boolean has three states: true, false, and unset. An unset Boolean is neither true nor false. It means 'not yet determined' or 'we do not know yet'. This is a fundamental difference.\n\nConsider a medical test result. 'Negative' (false) and 'we have not run the test yet' (unset) are completely different states. In Java, Python, or Go, an uninitialised boolean defaults to false, which silently conflates 'no' with 'unknown'. EK9 makes the distinction explicit.\n\nCREATING BOOLEANS\n  ready <- true\n  failed <- false\n  unknown <- Boolean()\nThe first two are set. The third exists but is unset. Use ? to check:\n  if ready?\n    stdout.println(\"ready has a value\")\n  if unknown?\n    stdout.println(\"this will not print\")\n\nLOGICAL OPERATORS\nEK9 provides four logical operators:\n  and   Logical conjunction: true and false gives false\n  or    Logical disjunction: true or false gives true\n  xor   Exclusive or: true xor true gives false, true xor false gives true\n  not   Logical negation: written as 'not' keyword\n  ~     Complement operator: ~true gives false, ~false gives true\n\nThese operators work on set Boolean values. If either operand is unset, the result is unset. This propagates the 'unknown' state correctly rather than silently treating it as false.\n\nCOMPARISON OPERATORS\n  ==    Equality: true == true gives true\n  <>    Inequality: true <> false gives true\n  <=>   Comparison: returns Integer for ordering\n\nOTHER OPERATORS\n  $     String conversion: $ready gives \"true\"\n  $$    JSON conversion\n  :=:   Copy assignment\n  :^:   Replace assignment\n  :~:   Merge assignment\n  +=    Accumulate (logical or)\n  +     Addition (logical or, pure)\n  |     Pipeline operator\n\nBOOLEAN FROM STRING\nYou can construct a Boolean from a String:\n  fromText <- Boolean(\"true\")\n  invalid <- Boolean(\"maybe\")\nIf the string is not 'true' or 'false', the Boolean becomes unset rather than throwing an exception. This follows the tri-state pattern: invalid input produces an unset value you can check with ?.\n\nWHY TRI-STATE MATTERS\nIn Java: boolean defaults to false. Did the user decline, or did we forget to ask?\nIn Python: bool defaults to False. Is the flag off, or was it never configured?\nIn Go: bool defaults to false. Is the check complete with a negative result, or was it never performed?\nIn JavaScript: undefined, null, and false are all falsy but mean different things.\n\nEK9 eliminates this ambiguity. Boolean() is unset. Boolean(true) is true. Boolean(false) is false. Three distinct states, three distinct meanings.\n\nFor locale-specific Boolean display, see Q44 (How does the Locale type work in EK9?).\n\nSee also Q29 (What is the default value of an unset variable?) for the full tri-state model. See Q47 for how the ? operator is used with Optional types. See Q244 for boolean and bitwise operators (and, or, xor, ~).","ek9Example":"defines module qa.boolean.values\n\n  defines program\n    BooleanDemo()\n      stdout <- Stdout()\n\n      // Three states\n      ready <- true\n      failed <- false\n      unknown <- Boolean()\n\n      stdout.println(`ready: ${ready}, is set: ${ready?}`)\n      stdout.println(`failed: ${failed}, is set: ${failed?}`)\n      stdout.println(`unknown is set: ${unknown?}`)\n\n      // Logical operators\n      both <- ready and failed\n      either <- ready or failed\n      exclusive <- ready xor failed\n      flipped <- ~ready\n\n      stdout.println(`and: ${both}, or: ${either}`)\n      stdout.println(`xor: ${exclusive}, complement: ${flipped}`)\n\n      // Boolean from String\n      fromText <- Boolean(\"true\")\n      invalid <- Boolean(\"maybe\")\n      stdout.println(`from text: ${fromText?}, invalid: ${invalid?}`)\n\n      // Guarded assignment\n      unknown :=? true\n      stdout.println(`unknown after guard: ${unknown}`)","migrationContext":"Java: boolean defaults to false, Boolean wrapper can be null (NPE risk). Python: bool defaults to False, truthy/falsy rules for other types. Go: bool defaults to false, no nullable bool. JavaScript: undefined/null/false all falsy with different semantics. Rust: bool is true/false, Option<bool> for tri-state. C#: bool defaults to false, Nullable<bool> for tri-state. EK9: Boolean has three states (true, false, unset), logical operators (and, or, xor, not, ~), unset propagates through operations.","keywords":["and","beginner","bool","boolean","complement","false","first","intro","logical","migrate","not","or","start","tri-state","tristate","true","unset","xor"],"primaryTopics":["boolean","true false","bool"],"typicalErrors":[{"error":"E07620","correct":"both <- ready and failed","incorrect":"both <- ready and 42","explanation":"EK9 logical operators require Boolean operands. Using an Integer where a Boolean is expected triggers E07620 — type incompatibility. Use a comparison operator to produce a Boolean. See ek9 -h E07620 for details."}],"companions":[]}
{"id":31,"category":"Getting Started","question":"How do I work with Date and Time?","url":"https://ek9.io/qa/QA0031.html","alternatePhrasings":["What date and time types does EK9 have?","How do I use date literals in EK9?","How does date arithmetic work in EK9?"],"answer":"EK9 has built-in Date and Time types with literal syntax, calendar-safe arithmetic, and rich operators. No imports or third-party libraries needed.\n\nDATE: Calendar Date Without Time\nLiteral syntax: 2024-01-15, 1971-02-01\nConstructor forms: Date(2020, 10, 03) or Date(daysFromEpoch) or Date(stringValue)\nUnset constructor: Date() creates an unset date (not today). Get today: Date().today() returns the current date.\nAccessors: year(), month(), day(), dayOfMonth(), dayOfWeek(), dayOfYear()\nArithmetic with Duration:\n  nextWeek <- birthday + P7D\n  anniversary <- wedding + P1Y1M4D\nSubtract two Dates to get a Duration:\n  age <- today - birthday\nThis is calendar-safe: adding P1M to January 31st gives February 28th (or 29th in a leap year), not an invalid date.\n\nTIME: Time of Day Without Date\nLiteral syntax: 09:30:00, 14:15, 12:00:01\nConstructor forms: Time(12, 00) or Time(12, 00, 01)\nUnset constructor: Time() creates an unset time. Time().now() returns the current time.\nConvenience: Time().startOfDay() gives 00:00:00, Time().endOfDay() gives 23:59:59\nAccessors: hour(), minute(), second()\nArithmetic with Duration:\n  later <- morning + PT3H\nTime wraps at 24 hours: adding PT3H to 22:00 gives 01:00, not 25:00.\nSubtract two Times to get a Duration:\n  workHours <- 14:15 - 09:30:00\n\nCOMPARISON AND TRI-STATE\nBoth types support ==, <>, <, >, <=, >=, <=>. Like all EK9 types, Date and Time can be unset (see Q29). An unset date is different from any specific date.\n\nDateTime combines date, time, and timezone into a single type (see Q92). Duration (P1Y1M4D) and Millisecond (500ms) are built-in temporal span types (see Q32, Q41). All temporal types support locale-aware formatting via the Locale type (see Q44).\n\nUse 'ek9 -h Date' and 'ek9 -h Time' to see the full API for each type.\n\nSee Q32 for duration. See Q92 for datetime operations. See Q536-Q556 for deep-dive date, time, timezone, and duration coverage.","ek9Example":"defines module qa.date.time\n\n  defines program\n    DateTimeDemo()\n      stdout <- Stdout()\n\n      // === DATE ===\n\n      // Date literals (ISO 8601)\n      birthday <- 1971-02-01\n      wedding <- 2020-10-03\n      stdout.println(`Birthday: ${birthday}`)\n      stdout.println(`Wedding: ${wedding}`)\n\n      // Constructor form\n      sameWedding <- Date(2020, 10, 03)\n      stdout.println(`Same wedding: ${sameWedding == wedding}`)\n\n      // Date() is unset, Date().today() gets current date\n      unsetDate <- Date()\n      stdout.println(`Unset date isSet: ${unsetDate?}`)\n      currentDate <- Date().today()\n      stdout.println(`Today: ${currentDate}`)\n\n      // Date accessors\n      stdout.println(`Year: ${birthday.year()}, Month: ${birthday.month()}, Day: ${birthday.day()}`)\n      stdout.println(`Day of week: ${birthday.dayOfWeek()}, Day of year: ${birthday.dayOfYear()}`)\n\n      // Date + Duration arithmetic\n      nextWeek <- birthday + P7D\n      lastWeek <- birthday - P7D\n      stdout.println(`Week after: ${nextWeek}, Week before: ${lastWeek}`)\n\n      // Compound duration: one year, one month, four days\n      anniversary <- wedding + P1Y1M4D\n      stdout.println(`Anniversary + P1Y1M4D: ${anniversary}`)\n\n      // Date - Date gives Duration\n      gap <- anniversary - wedding\n      stdout.println(`Gap: ${gap}`)\n\n      // Compound assignment\n      mutableDate <- 2024-01-15\n      mutableDate += P2W\n      stdout.println(`After += P2W: ${mutableDate}`)\n\n      // === TIME ===\n\n      // Time literals\n      morning <- 09:30:00\n      afternoon <- 14:15\n      stdout.println(`Morning: ${morning}, Afternoon: ${afternoon}`)\n\n      // Constructor forms\n      noon <- Time(12, 00)\n      precise <- Time(12, 00, 01)\n      stdout.println(`Noon: ${noon}, Precise: ${precise}`)\n\n      // Time() is unset\n      unsetTime <- Time()\n      stdout.println(`Unset time isSet: ${unsetTime?}`)\n\n      // startOfDay and endOfDay\n      dayStart <- Time().startOfDay()\n      dayEnd <- Time().endOfDay()\n      stdout.println(`Day start: ${dayStart}, Day end: ${dayEnd}`)\n\n      // Time accessors\n      stdout.println(`Hour: ${morning.hour()}, Minute: ${morning.minute()}, Second: ${morning.second()}`)\n\n      // Time + Duration arithmetic\n      later <- morning + PT3H\n      earlier <- morning - PT1H\n      stdout.println(`+3h: ${later}, -1h: ${earlier}`)\n\n      // Time - Time gives Duration\n      workHours <- afternoon - morning\n      stdout.println(`Work hours: ${workHours}`)\n\n      // Comparisons (use variables from function calls to avoid tautological conditions)\n      if birthday < currentDate\n        stdout.println(\"Birthday is in the past\")\n      currentTime <- Time().now()\n      if morning < currentTime\n        stdout.println(\"Morning is before current time\")","migrationContext":"Java: java.util.Date (broken), java.time.LocalDate/LocalTime (Java 8), no literals, verbose factory methods. Python: datetime.date/datetime.time, no literals, limited arithmetic. Rust: no built-in, chrono crate required. Go: time.Time only, no separate Date/Time, bizarre reference date format. JavaScript: Date object notoriously broken (months 0-indexed, mutable), no Time type. EK9: Date and Time built-in with literal syntax, calendar-safe arithmetic, Duration interop, no imports needed.","keywords":["accessor","arithmetic","beginner","calendar","date","day","duration","first","hour","intro","literal","minute","month","second","start","time","today","year"],"primaryTopics":["date","time","date type"],"typicalErrors":[{"error":"E50060","correct":"birthday.year()","incorrect":"birthday.getYear()","explanation":"EK9 built-in types use short method names without 'get' prefix. Date has year(), month(), day() not getYear(), getMonth(), getDay(). Triggers E50060 — method not resolved. See ek9 -h Date for the full API."}],"companions":[]}
{"id":32,"category":"Getting Started","question":"How do I work with Duration?","url":"https://ek9.io/qa/QA0032.html","alternatePhrasings":["What is the ISO 8601 duration format?","How do Duration literals work in EK9?","How do I represent time spans in EK9?","What does PT1H30M mean?"],"answer":"Duration represents time spans using ISO 8601 literals. Starts with P, date components Y/M/W/D, T separates time H/M/S. M means months before T, minutes after T.\n\nEXAMPLES\n  P1Y6M15D one year six months fifteen days, PT1H30M one hour thirty minutes, P3W three weeks (=P21D).\n\nArithmetic: +, -, */by Integer or Float, compound assignment. Accessors: .years(), .months(), .days(), .hours(), .minutes(), .seconds(). Comparison: ==, <>, <, >, <=, >=, <=>.\n\nUse 'ek9 -h Duration' for the full API.\n\nSee Q31 for Date/Time. See Q41 for Millisecond. See Q44 for locale formatting. See Q539 for date differences.","ek9Example":"defines module qa.duration\n\n  defines program\n    DurationDemo()\n      stdout <- Stdout()\n\n      // === ISO 8601 DURATION FORMAT EXPLAINED ===\n\n      // The 'P' prefix means \"Period\" - every duration starts with P\n      // After P, date components: Y=years, M=months, W=weeks, D=days\n      // The 'T' separator introduces time components: H=hours, M=minutes, S=seconds\n      // Note: M means months BEFORE the T, and minutes AFTER the T\n\n      // Date-only durations (no T needed)\n      oneYear <- P1Y\n      twoMonths <- P2M\n      tenDays <- P10D\n      threeWeeks <- P3W\n      stdout.println(`Year: ${oneYear}, Months: ${twoMonths}, Days: ${tenDays}, Weeks: ${threeWeeks}`)\n\n      // Weeks convert to days: P3W equals P21D\n      require threeWeeks == P21D\n\n      // Time-only durations (PT prefix - P then T then time components)\n      oneHour <- PT1H\n      thirtyMinutes <- PT30M\n      fortyFiveSeconds <- PT45S\n      stdout.println(`Hour: ${oneHour}, Minutes: ${thirtyMinutes}, Seconds: ${fortyFiveSeconds}`)\n\n      // Combined time: PT1H30M15S = 1 hour, 30 minutes, 15 seconds\n      combinedTime <- PT1H30M15S\n      stdout.println(`Combined time: ${combinedTime}`)\n\n      // Combined date: P1Y6M15D = 1 year, 6 months, 15 days\n      combinedDate <- P1Y6M15D\n      stdout.println(`Combined date: ${combinedDate}`)\n\n      // Full combined: P2Y3M10DT4H30M = 2 years, 3 months, 10 days, 4 hours, 30 minutes\n      // The T separates date parts from time parts\n      fullCombined <- P2Y3M10DT4H30M\n      stdout.println(`Full: ${fullCombined}`)\n\n      // Complex with weeks: P2Y6W3DT8H5M8S\n      // = 2 years, 6 weeks, 3 days (=45 days), 8 hours, 5 minutes, 8 seconds\n      withWeeks <- P2Y6W3DT8H5M8S\n      stdout.println(`With weeks: ${withWeeks}`)\n\n      // === COMPONENT ACCESSORS ===\n\n      d1 <- P2Y3M15DT4H30M45S\n      stdout.println(`Years: ${d1.years()}, Months: ${d1.months()}, Days: ${d1.days()}`)\n      stdout.println(`Hours: ${d1.hours()}, Minutes: ${d1.minutes()}, Seconds: ${d1.seconds()}`)\n\n      // === DURATION ARITHMETIC ===\n\n      // Addition and subtraction\n      morning <- PT2H30M\n      afternoon <- PT4H15M\n      totalWork <- morning + afternoon\n      difference <- afternoon - morning\n      stdout.println(`Total: ${totalWork}, Difference: ${difference}`)\n\n      // Multiply by Integer or Float\n      doubled <- PT1H * 2\n      tripled <- PT1H * 3\n      scaled <- PT1H * 1.5\n      stdout.println(`Doubled: ${doubled}, Tripled: ${tripled}, Scaled: ${scaled}`)\n\n      // Divide by Integer or Float\n      halved <- PT2H / 2\n      thirded <- PT3H / 3\n      stdout.println(`Halved: ${halved}, Thirded: ${thirded}`)\n\n      // Compound assignment operators\n      accumulator <- PT1H\n      accumulator += PT30M\n      require accumulator == PT1H30M\n      accumulator -= PT15M\n      require accumulator == PT1H15M\n      accumulator *= 2\n      require accumulator == PT2H30M\n      accumulator /= 2\n      require accumulator == PT1H15M\n      stdout.println(`After compound ops: ${accumulator}`)\n\n      // Negation\n      positive <- PT5H\n      negative <- -positive\n      stdout.println(`Positive: ${positive}, Negative: ${negative}`)\n\n      // === NEGATIVE DURATIONS ===\n\n      // Subtracting a later time from an earlier time gives a negative duration\n      negDur <- 06:15:12 - 12:04:09\n      stdout.println(`Negative: ${negDur}`)\n      require negDur == PT-5H-48M-57S\n\n      // === DURATION FROM STRING ===\n\n      // Construct from a string representation\n      fromString <- Duration(\"P1Y6M\")\n      stdout.println(`From string: ${fromString}`)\n\n      // Convert to string with $ operator\n      asString <- $fullCombined\n      stdout.println(`As string: ${asString}`)\n      roundTrip <- Duration(asString)\n      require roundTrip == fullCombined\n\n      // === COMPARISON ===\n\n      short <- PT30M\n      long <- PT2H\n      require short < long\n      require long > short\n      require short <> long\n      require PT1H == PT1H\n      require PT30M <= PT1H\n      require PT2H >= PT1H\n      stdout.println(`30min < 2h: ${short < long}`)\n\n      // === UNSET DURATION ===\n\n      unset <- Duration()\n      require ~unset?\n      stdout.println(`Unset duration isSet: ${unset?}`)\n\n      // === USING DURATIONS WITH DATES AND TIMES ===\n\n      // Date arithmetic with complex durations\n      startDate <- 2020-01-15\n      later <- startDate + P1Y6M10D\n      stdout.println(`Date + P1Y6M10D: ${later}`)\n\n      earlier <- startDate - P2M\n      stdout.println(`Date - P2M: ${earlier}`)\n\n      // Time arithmetic with wrapping\n      startTime <- 22:00\n      wrapped <- startTime + PT3H\n      stdout.println(`22:00 + PT3H wraps to: ${wrapped}`)\n\n      // DateTime arithmetic\n      meeting <- 2024-06-15T10:30:00Z\n      meetingEnd <- meeting + PT1H30M\n      stdout.println(`Meeting ends: ${meetingEnd}`)\n\n      // Subtracting temporal values produces a Duration\n      dateGap <- 2024-12-25 - 2024-01-01\n      stdout.println(`Days until Christmas: ${dateGap}`)\n\n      timeGap <- 17:30 - 09:00\n      stdout.println(`Work day: ${timeGap}`)\n\n      // === MILLISECOND CONVERSION ===\n\n      // Millisecond to Duration\n      timeout <- 5400ms\n      asDuration <- timeout.duration()\n      stdout.println(`5400ms as duration: ${asDuration}`)\n\n      // Duration to Millisecond\n      fromDuration <- Millisecond(P2W)\n      stdout.println(`P2W as millis: ${fromDuration}`)","migrationContext":"Java: verbose Duration.ofHours()/Period split. Python: timedelta, no months/years. Go/Rust/JS: no duration literals. EK9: ISO 8601 PT1H30M literals, unified type years through seconds.","keywords":["8601","arithmetic","beginner","days","duration","first","format","hours","interval","intro","iso","literal","millisecond","minutes","months","period","seconds","start","timespan","timing","weeks","years"],"primaryTopics":["duration","time duration","time interval"],"typicalErrors":[{"error":"E50060","correct":"d1.hours()","incorrect":"d1.getHours()","explanation":"EK9 Duration uses short method names: hours(), minutes(), seconds() not getHours(), getMinutes(), getSeconds(). Triggers E50060 — method not resolved. See ek9 -h Duration for the full API."}],"companions":[]}
{"id":33,"category":"Getting Started","question":"How do I work with regular expressions?","url":"https://ek9.io/qa/QA0033.html","alternatePhrasings":["Does EK9 have regex support?","How do I match patterns in strings?","How do I split strings with a regex in EK9?","How do I extract groups from a regex match?"],"answer":"EK9 has a built-in RegEx type with /pattern/ literal syntax (same as JavaScript), avoiding double-escaping.\n\nREGEX LITERALS\n  digitPattern <- /\\d+/\n  emailPattern <- /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$/\nNo string escaping needed: /\\d+/ is what you mean. Java requires Pattern.compile(\"\\\\d+\").\n\nMATCHING\nBidirectional 'matches' operator:\n  require 'hello123' matches /[a-z]+\\d+/\n  require /[a-z]+\\d+/ matches 'hello123'\nNegate with 'not matches':\n  require 'hello' not matches /\\d+/\n\nSPLITTING STRINGS\nSplit with regex delimiter, works both directions:\n  parts <- 'one:two:three'.split(/:/)\n  alsoParts <- /:/.split('one:two:three')\nBoth produce List of String.\n\nGROUP EXTRACTION\nCapture groups extract matched portions via split():\n  groups <- text.split(/\\{(.*?)(\\d+)(.*)/)\nIf matched, groups is List of String per capture group. If not matched, groups is unset (see Q29).\nAccess: groups.getOrDefault(0, \"\") or stream: cat groups | skip 1 | head 1 | collect as String\n\nESCAPING IN LITERALS\n/\\\\/ matches literal backslash. /\\// matches literal forward slash.\nShortcuts: \\d \\w \\s . Quantifiers: ? * + {n} {n,m}. Anchors: ^ $ \\b. Classes: [a-z] [^0-9].\n\nREGEX COMPOSITION\nBuild patterns with + operator:\n  combined <- /^Hello/ + / / + /World$/\nExtend with +=:\n  pattern <- /^Start/\n  pattern += /.*End$/\n\nCOMPARISON AND IDENTITY\n== and <> compare patterns. ? checks if set. $ converts to string. length returns pattern length.\n\nUse 'ek9 -h RegEx' and 'ek9 -h String' for the full API.\n\nSee Q37 for string operations. See Q29 for tri-state and unset group extraction.","ek9Example":"defines module qa.regex\n\n  defines program\n    RegExDemo()\n      stdout <- Stdout()\n\n      // === REGEX LITERALS ===\n\n      // Regex literals use /pattern/ syntax - like JavaScript\n      // No double-escaping needed (unlike Java's Pattern.compile(\"\\\\d+\"))\n      digitPattern <- /\\d+/\n      emailPattern <- /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$/\n      stdout.println(`Digit pattern: ${digitPattern}`)\n      require \"user@example.com\" matches emailPattern\n      require \"not-an-email\" not matches emailPattern\n\n      // === MATCHING ===\n\n      // The 'matches' operator works in both directions\n      require \"hello123\" matches /[a-z]+\\d+/\n      require /[a-z]+\\d+/ matches \"hello123\"\n\n      // 'not matches' for negation\n      require \"hello\" not matches /\\d+/\n\n      // Match name variants with character classes and alternation\n      namePattern <- /[S|s]te(?:ven?|phen)/\n      require \"Steve\" matches namePattern\n      require \"steve\" matches namePattern\n      require \"Stephen\" matches namePattern\n      require \"Steven\" matches namePattern\n      require \"Stephene\" not matches namePattern\n\n      // Character class with quantifier\n      sixChars <- /[a-zA-Z0-9]{6}/\n      require sixChars matches \"arun32\"\n      require sixChars not matches \"kkvarun32\"\n      require sixChars not matches \"arun$2\"\n\n      // === SPLITTING STRINGS ===\n\n      // Split works both directions: string.split(regex) or regex.split(string)\n      colonRegEx <- /:/\n      colonDelimited <- \"one:two:three:four:five\"\n\n      fromString <- colonDelimited.split(colonRegEx)\n      fromRegEx <- colonRegEx.split(colonDelimited)\n      require fromString == fromRegEx\n      stdout.println(`Split: ${fromString}`)\n      require $fromString == \"one,two,three,four,five\"\n\n      // === GROUP EXTRACTION ===\n\n      // Capture groups in the pattern extract matched portions\n      text <- \"This is a sample Text 1234 with numbers in between.\"\n      groupPattern <- /\\{(.*?)(\\d+)(.*)/\n      groups <- text.split(groupPattern)\n\n      stdout.println(`Groups: ${groups}`)\n      require length groups == 3\n      require groups.getOrDefault(0, \"\") == \"This is a sample Text \"\n      require groups.getOrDefault(2, \"\") == \" with numbers in between.\"\n\n      // Extract specific group using stream pipeline\n      justNumber <- cat groups | skip 1 | head 1 | collect as String\n      require $justNumber == \"1234\"\n      stdout.println(`Extracted number: ${justNumber}`)\n\n      // Convert extracted text to Integer\n      asInteger <- Integer(justNumber)\n      require asInteger == 1234\n      stdout.println(`As integer: ${asInteger}`)\n\n      // Get last two groups\n      lastTwo <- cat groups | tail 2 | collect as List of String\n      require length lastTwo == 2\n\n      // === NO MATCH RETURNS UNSET ===\n\n      noNumbers <- \"No digits here at all.\"\n      noMatch <- noNumbers.split(groupPattern)\n      require ~noMatch?\n      stdout.println(`No match isSet: ${noMatch?}`)\n\n      // === ESCAPING IN LITERALS ===\n\n      // Backslash in regex literal: /\\\\/ matches a literal backslash\n      backslashPattern <- /^Some\\\\Thing$/\n      require backslashPattern matches \"Some\\Thing\"\n\n      // Forward slash in regex literal: /\\// matches a literal forward slash\n      slashPattern <- /^Some\\/Thing$/\n      require slashPattern matches \"Some/Thing\"\n\n      // Fraction matching with escaped slash\n      fractionPattern <- /.*\\/.*/\n      require fractionPattern matches \"3/4\"\n\n      // === REGEX COMPOSITION ===\n\n      // Build patterns with + operator\n      prefix <- /^Hello/\n      combined <- prefix + / / + /World$/\n      require combined matches \"Hello World\"\n      stdout.println(`Combined: ${combined}`)\n\n      // += to extend a pattern\n      growing <- /^Start/\n      growing += /.*End$/\n      require growing matches \"Start and End\"\n\n      // === COMMON PATTERNS ===\n\n      // Phone number\n      require \"555-1234\" matches /^\\d{3}-\\d{4}$/\n\n      // UK postcode (simplified)\n      require \"SW1A 1AA\" matches /^[A-Z]{1,2}\\d[A-Z\\d]?\\s?\\d[A-Z]{2}$/\n\n      // ISO date format\n      require \"2024-06-15\" matches /^\\d{4}-\\d{2}-\\d{2}$/\n\n      stdout.println(\"All regex tests passed\")","migrationContext":"Java: Pattern.compile() double-escaping, Pattern/Matcher ceremony. Python: re.compile(r'pattern'). JavaScript: /pattern/ (EK9 follows this). Rust/Go: no literal syntax. EK9: /pattern/ literal, bidirectional matches, split both directions, no double-escaping.","keywords":["anchor","beginner","capture","character","class","escape","expression","first","group","intro","literal","match","matches","migrate","pattern","quantifier","regex","regular","split","start"],"primaryTopics":["regex","regular expression","pattern matching string"],"typicalErrors":[{"error":"E50001","correct":"digitPattern <- /\\d+/","incorrect":"matches <- /\\d+/","explanation":"EK9 rejects variable names that shadow operator keywords. 'matches' is a reserved operator keyword. Use descriptive names like 'digitPattern' or 'emailPattern'. See ek9 -h E50001 for details."}],"companions":[]}
{"id":34,"category":"Getting Started","question":"How do I work with colours?","url":"https://ek9.io/qa/QA0034.html","alternatePhrasings":["Does EK9 have a built-in colour type?","How do I manipulate RGB colours in EK9?","How does HSL colour manipulation work in EK9?","How do I blend colours in EK9?"],"answer":"Colour is EK9's built-in type for RGB colour values with hex literal syntax. No imports needed. Uses British spelling 'Colour' consistently.\n\nLITERALS\n  #FF0000         Pure red (6-digit RGB)\n  #FF186276       With alpha (8-digit ARGB)\n\nOUTPUT FORMATS\n  .RGB() returns '#RRGGBB', .RGBA() returns '#RRGGBBAA', .ARGB() returns '#AARRGGBB'.\n\nHSL MANIPULATION\nImmutable methods return NEW colours: withLightness(80), withSaturation(90), withHue(200). Access HSL values with .hue(), .saturation(), .lightness(). Control transparency with withOpaque(percentage).\n\nCOLOUR ARITHMETIC\nAdd/subtract to blend/remove RGB components (auto-clamped to 0-255):\n  purple <- #FF0000 + #0000FF\n\nBITS INTEROP\n  .bits() extracts 32-bit ARGB value. Colour(bitsValue) constructs from Bits.\n\nComparison: ==, <>, <, >, <=, >=, <=>.\n\nUse 'ek9 -h Colour' to see the full API.\n\nSee Q37 for string interpolation. See Q40 for Bits interop.","ek9Example":"defines module qa.colour\n\n  defines program\n    ColourDemo()\n      stdout <- Stdout()\n\n      // === COLOUR LITERALS ===\n\n      // Hex literal with alpha channel: #AARRGGBB (ARGB format)\n      opaqueBlue <- #FF186276\n      stdout.println(`Opaque blue: ${opaqueBlue}`)\n\n      // Hex literal without alpha: #RRGGBB (RGB format)\n      pureRed <- #FF0000\n      pureGreen <- #00FF00\n      pureBlue <- #0000FF\n      stdout.println(`Red: ${pureRed}, Green: ${pureGreen}, Blue: ${pureBlue}`)\n\n      // Unset colour\n      unsetColour <- Colour()\n      require ~unsetColour?\n\n      // === OUTPUT FORMATS ===\n\n      // Three different hex string formats\n      stdout.println(`RGB: ${opaqueBlue.RGB()}`)\n      stdout.println(`RGBA: ${opaqueBlue.RGBA()}`)\n      stdout.println(`ARGB: ${opaqueBlue.ARGB()}`)\n\n      // === HSL ACCESSORS ===\n\n      // Hue (0-360), Saturation (0-100%), Lightness (0-100%)\n      stdout.println(`Hue: ${opaqueBlue.hue()}`)\n      stdout.println(`Saturation: ${opaqueBlue.saturation()}`)\n      stdout.println(`Lightness: ${opaqueBlue.lightness()}`)\n\n      // === HSL MANIPULATION ===\n\n      // Each with* method returns a NEW colour (immutable)\n      lighterBlue <- opaqueBlue.withLightness(80)\n      stdout.println(`Lighter: ${lighterBlue}`)\n\n      moreSaturated <- lighterBlue.withSaturation(90)\n      stdout.println(`More saturated: ${moreSaturated}`)\n\n      shiftedHue <- opaqueBlue.withHue(200)\n      stdout.println(`Shifted hue: ${shiftedHue}`)\n\n      // === ALPHA CHANNEL (TRANSPARENCY) ===\n\n      // withOpaque(percentage) sets opacity: 100=fully opaque, 0=fully transparent\n      semiTransparent <- opaqueBlue.withOpaque(50)\n      stdout.println(`50% opaque: ${semiTransparent}`)\n\n      mostlyOpaque <- opaqueBlue.withOpaque(80)\n      stdout.println(`80% opaque: ${mostlyOpaque}`)\n\n      // === COLOUR ARITHMETIC ===\n\n      // Add colours: blends RGB components (clamped to 0-255)\n      purple <- pureRed + pureBlue\n      stdout.println(`Red + Blue = Purple: ${purple}`)\n\n      yellow <- pureRed + pureGreen\n      stdout.println(`Red + Green = Yellow: ${yellow}`)\n\n      cyan <- pureGreen + pureBlue\n      stdout.println(`Green + Blue = Cyan: ${cyan}`)\n\n      // Subtract colours: removes RGB components (clamped to 0)\n      backToRed <- purple - pureBlue\n      stdout.println(`Purple - Blue = Red: ${backToRed}`)\n      require backToRed == pureRed\n\n      backToGreen <- yellow - pureRed\n      stdout.println(`Yellow - Red = Green: ${backToGreen}`)\n      require backToGreen == pureGreen\n\n      // Subtract specific amounts of a channel\n      lessRed <- moreSaturated - #9A0000\n      stdout.println(`Less red: ${lessRed}`)\n\n      moreBlue <- lessRed + #00001D\n      stdout.println(`More blue: ${moreBlue}`)\n\n      // === BITS INTEROP ===\n\n      // Convert Colour to Bits and back\n      colourBits <- opaqueBlue.bits()\n      stdout.println(`As bits: ${colourBits}`)\n\n      fromBits <- Colour(colourBits)\n      require fromBits == opaqueBlue\n\n      // Build colour from raw bit patterns\n      redBits <- 0b11111111111111110000000000000000\n      redFromBits <- Colour(redBits)\n      stdout.println(`Red from bits: ${redFromBits}`)\n\n      // === PROGRAMMATIC LIGHTENING ===\n\n      // Calculate a percentage lighter\n      currentLightness <- opaqueBlue.lightness()\n      proposedLightness <- currentLightness * 1.9\n      clampedLightness <- proposedLightness <? 100\n      percentLighter <- opaqueBlue.withLightness(clampedLightness)\n      stdout.println(`90% lighter: ${percentLighter}`)\n\n      // === COMPARISON ===\n\n      require pureRed <> pureBlue\n      require #FF0000 == #FF0000\n      stdout.println(`Red == Red: ${pureRed == #FF0000}`)","migrationContext":"Java: java.awt.Color tied to AWT, no literals, no HSL. CSS: #hex but declarative only. Python/Rust/Go/JS: no built-in colour type. EK9: built-in #RRGGBB literals, HSL methods, colour arithmetic with clamping, Bits interop.","keywords":["alpha","argb","beginner","bits","blend","color","colour","first","hex","hsl","hue","intro","lightness","migrate","opacity","rgb","rgba","saturation","start","transparent"],"primaryTopics":["colour","color","colour type"],"typicalErrors":[{"error":"E50060","correct":"opaqueBlue.withLightness(80)","incorrect":"opaqueBlue.setLightness(80)","explanation":"EK9 Colour uses 'withLightness' not 'setLightness'. Methods return new immutable values rather than mutating. Check available methods with 'ek9 -h Colour'. See ek9 -h E50060 for details."}],"companions":[]}
{"id":35,"category":"Getting Started","question":"How do I work with money and currency in EK9?","url":"https://ek9.io/qa/QA0035.html","alternatePhrasings":["Does EK9 have a built-in money type?","How does EK9 handle currency arithmetic and rounding?","What arithmetic operations can I do with Money in EK9?","How does rounding work for Money in EK9?"],"answer":"EK9 has a built-in Money type with literal syntax, automatic rounding, and per-currency precision. No financial library needed.\n\nMONEY LITERALS\nFormat: amount#CURRENCY using ISO 4217 three-letter currency codes:\n  10#GBP            British pounds (auto-padded to 10.00#GBP)\n  30.20#USD         US dollars\n  6798.9288#CLF     Chilean Unidad de Fomento (4 decimal places)\n  1500#JPY          Japanese yen (0 decimal places)\nThe compiler knows every ISO 4217 currency and its correct decimal precision. GBP and USD use 2 decimals, JPY uses 0, CLF uses 4. Amounts are automatically padded or rounded to the correct precision for the currency. Money() creates an unset money value.\n\nAUTOMATIC ROUNDING\nEK9 uses HALF_UP rounding — the rounding everyone learns in school. When the digit being dropped is 5 or above, round away from zero:\n  99.51#GBP / 2 = 49.755 rounds to 49.76#GBP\nRounding is automatic and uses each currency's correct precision. You never need to specify a rounding mode or scale.\n\nARITHMETIC OPERATORS\nAddition and subtraction work between money values of the SAME currency:\n  money + money      Addition (10#GBP + 89.51#GBP = 99.51#GBP)\n  money - money      Subtraction (500#GBP - 100#GBP = 400#GBP)\nMultiplication and division scale by a Float or Integer:\n  money * float      49.76#GBP * -8.754 = -435.60#GBP\n  money / integer    99.51#GBP / 2 = 49.76#GBP (HALF_UP)\nDividing money by money gives the ratio as a Float:\n  935.60#GBP / 155.93#GBP = 6.0001 (a Float, not Money)\n\nCOMPOUND ASSIGNMENT\n  working += 4.07#GBP     Add to existing\n  working -= 0.56#GBP     Subtract from existing\n  working *= 0.666        Multiply by factor\n  working /= 4            Divide by number\n\nUNARY OPERATORS\n  -amount          Negation\n  abs amount       Absolute value\n  sqrt amount      Square root (rounded to currency precision)\n  amount ^ 6       Power (exponentiation)\n\nCOMPARISON\nMoney supports ==, <>, <, >, <=, >=, <=> for same-currency comparison.\n\nSee Q149 for currency safety (mixed currency, division by zero, and why Money is built-in). See Q150 for currency conversion and locale formatting. Use 'ek9 -h Money' to see the full API.\n\nSee Q36 for dimension. See Q149 for money safety.","ek9Example":"defines module qa.money\n\n  defines program\n    MoneyDemo()\n      stdout <- Stdout()\n\n      // === MONEY LITERALS ===\n\n      // Format: amount#CURRENCY using ISO 4217 codes\n      tenPounds <- 10#GBP\n      thirtyDollars <- 30.20#USD\n      chilean <- 6798.9288#CLF\n      stdout.println(`GBP: ${tenPounds}`)\n      stdout.println(`USD: ${thirtyDollars}`)\n      stdout.println(`CLF: ${chilean}`)\n\n      // Auto-padding: 10#GBP equals 10.00#GBP\n      require tenPounds == 10.00#GBP\n\n      // Unset money\n      unsetMoney <- Money()\n      require ~unsetMoney?\n\n      // === ARITHMETIC WITH AUTO-ROUNDING ===\n\n      // Addition (same currency only)\n      total <- tenPounds + 89.51#GBP\n      require total == 99.51#GBP\n      stdout.println(`Total: ${total}`)\n\n      // Division rounds using HALF_UP: 99.51 / 2 = 49.755 rounds to 49.76\n      halved <- total / 2\n      require halved == 49.76#GBP\n      stdout.println(`Halved (HALF_UP): ${halved}`)\n\n      // Multiply by Float\n      negativeAmount <- halved * -8.754\n      require negativeAmount == -435.60#GBP\n      stdout.println(`Negative: ${negativeAmount}`)\n\n      // Subtraction\n      recovery <- 500#GBP - negativeAmount\n      require recovery == 935.60#GBP\n\n      // Complex expression\n      calculated <- (recovery * 3) / 18\n      require calculated == 155.93#GBP\n      stdout.println(`Calculated: ${calculated}`)\n\n      // Money / Money gives Float (the ratio)\n      ratio <- recovery / calculated\n      stdout.println(`Ratio: ${ratio}`)\n\n      // === COMPOUND ASSIGNMENT ===\n\n      working <- 155.93#GBP\n      working += 4.07#GBP\n      require working == 160.00#GBP\n\n      working *= 0.666\n      require working == 106.56#GBP\n\n      working -= 0.56#GBP\n      require working == 106.00#GBP\n\n      working /= 4\n      require working == 26.50#GBP\n\n      // === UNARY OPERATORS ===\n\n      // Negation\n      working := -working\n      require working == -26.50#GBP\n\n      // Absolute value\n      working := abs working\n      require working == 26.50#GBP\n\n      // Square root\n      working := sqrt working\n      require working == 5.15#GBP\n\n      // Power\n      working := working ^ 6\n      require working == 18657.07#GBP\n      stdout.println(`After power: ${working}`)\n\n      // === COMPARISON ===\n\n      require tenPounds < total\n      require total > tenPounds\n      require tenPounds <> total\n      require tenPounds <= 10.00#GBP\n      require tenPounds >= 10.00#GBP","migrationContext":"Java: BigDecimal with explicit RoundingMode at every operation, verbose new BigDecimal(\"10.50\").multiply(rate).setScale(2, RoundingMode.HALF_UP), Currency class separate from amount, no literal syntax, mixed currency not detected. Python: decimal.Decimal requires context for rounding, no currency awareness, money libraries (py-moneyed) needed. JavaScript: IEEE 754 floating-point causes 0.1+0.2!=0.3 bugs, Dinero.js or similar needed. Ruby: no built-in, money gem needed. Go: no built-in, shopspring/decimal or similar. Rust: no built-in, rust_decimal crate. C#: decimal type has precision but no currency, no literal syntax for currency. EK9: built-in amount#CURRENCY literals, automatic HALF_UP rounding, per-currency precision, mixed currency safety, stream collection, currency conversion, no imports needed.","keywords":["arithmetic","beginner","convert","currency","decimal","exchange","financial","first","gbp","half_up","intro","iso4217","money","precision","rounding","start","usd"],"primaryTopics":["money","currency","money type"],"typicalErrors":[{"error":"E50060","correct":"tenPounds + 89.51#GBP","incorrect":"tenPounds.add(89.51#GBP)","explanation":"EK9 Money uses operators for arithmetic, not method calls. Use + for addition, - for subtraction, not .add() or .subtract(). Triggers E50060 — method not resolved. See ek9 -h Money for the full API."}],"companions":[]}
{"id":36,"category":"Getting Started","question":"How do I work with measurements and units in EK9?","url":"https://ek9.io/qa/QA0036.html","alternatePhrasings":["Does EK9 have a built-in unit type for measurements?","How does EK9 prevent unit mismatch errors?","How do I convert between units like miles and kilometres in EK9?","What is the Dimension type in EK9?"],"answer":"Dimension is EK9's built-in type for unit-aware measurements. Any number followed by a unit suffix becomes a literal: 1cm, 10px, 4.5em, 8mile. The unit suffix is freeform.\n\nSame-unit arithmetic works normally. Different-unit operations return unset (safety against unit mismatch). Scaling by number preserves the unit. Dimension/Dimension of same unit gives a Float ratio. Convert units with convert(): eightMiles.convert(1.609344km).\n\nOperators: +, -, *, /, ^, abs, sqrt, comparison, <?. Use #< to extract numeric value.\n\nUse 'ek9 -h Dimension' to see the full API.\n\nSee Q35 for Money. See Q39 for Integer/Float. See Q44 for locale formatting.","ek9Example":"defines module qa.dimension\n\n  defines program\n    DimensionDemo()\n      stdout <- Stdout()\n\n      // === DIMENSION LITERALS ===\n\n      // Format: number followed by unit suffix (any string)\n      oneCentimetre <- 1cm\n      tenPixels <- 10px\n      fourAndHalfEm <- 4.5em\n      oneAndHalfEm <- 1.5em\n      eightMiles <- 8mile\n      stdout.println(`cm: ${oneCentimetre}, px: ${tenPixels}, em: ${fourAndHalfEm}`)\n\n      // Unset dimension\n      unsetDim <- Dimension()\n      require ~unsetDim?\n\n      // === ARITHMETIC: SCALING BY NUMBER ===\n\n      // Multiply dimension by number — scales the amount, keeps the unit\n      doubled <- oneCentimetre * 2\n      require doubled == 2cm\n      stdout.println(`1cm * 2 = ${doubled}`)\n\n      // Divide dimension by number — scales down\n      divided <- tenPixels / 5\n      require divided == 2px\n      stdout.println(`10px / 5 = ${divided}`)\n\n      // Add a plain number to a dimension\n      wider <- fourAndHalfEm + 0.6\n      require wider == 5.1em\n      stdout.println(`4.5em + 0.6 = ${wider}`)\n\n      // === ARITHMETIC: SAME-UNIT ADDITION ===\n\n      // Add dimensions with the SAME unit\n      combined <- fourAndHalfEm + oneAndHalfEm\n      require combined == 6em\n      stdout.println(`4.5em + 1.5em = ${combined}`)\n\n      // === ARITHMETIC: DIMENSION / DIMENSION GIVES A NUMBER ===\n\n      // Dividing dimension by dimension of same unit gives a unitless Float\n      ratio <- fourAndHalfEm / oneAndHalfEm\n      require ratio == 3\n      stdout.println(`4.5em / 1.5em = ${ratio} (unitless)`)\n\n      // === SAFETY: DIFFERENT UNITS RETURN UNSET ===\n\n      // Cannot add centimetres to pixels — different units\n      invalidAdd <- oneCentimetre + tenPixels\n      require ~invalidAdd?\n      stdout.println(`cm + px isSet: ${invalidAdd?}`)\n\n      // Comparison across different units also returns unset\n      invalidCompare <- oneCentimetre <> tenPixels\n      require ~invalidCompare?\n\n      // === UNIT CONVERSION ===\n\n      // Define a conversion factor: 1 mile = 1.609344 km\n      numberOfKmInMiles <- 1.609344km\n      inKM <- eightMiles.convert(numberOfKmInMiles)\n      require inKM == 12.874752km\n      stdout.println(`${eightMiles} in km is ${inKM}`)\n\n      // But after conversion, original and converted have DIFFERENT units\n      // So adding them returns unset — must pick one unit system\n      stillInvalid <- eightMiles + inKM\n      require ~stillInvalid?\n\n      // === EXTRACT NUMERIC VALUE ===\n\n      // The #< operator extracts the number without the unit\n      numericValue <- #< inKM\n      require numericValue == 12.874752\n      stdout.println(`Numeric value without unit: ${numericValue}`)\n\n      // === UNARY OPERATORS ===\n\n      // Negation\n      returnJourney <- -eightMiles\n      stdout.println(`Return journey: ${returnJourney}`)\n\n      // Absolute value\n      require abs returnJourney == eightMiles\n\n      // Square root\n      sqrtEm <- sqrt combined\n      stdout.println(`sqrt 6em = ${sqrtEm}`)\n\n      // Power\n      squared <- inKM ^ 2\n      stdout.println(`Squared: ${squared}`)\n\n      // === COMPOUND ASSIGNMENT AND INCREMENT ===\n\n      working <- 5.1em\n      working += 0.9\n      require working == 6em\n      stdout.println(`After +=: ${working}`)\n\n      working++\n      require working == 7em\n      stdout.println(`After ++: ${working}`)\n\n      // === COMPARISON ===\n\n      require fourAndHalfEm < combined\n      require combined > fourAndHalfEm\n      require fourAndHalfEm <> combined\n      require fourAndHalfEm <= 4.5em\n      require fourAndHalfEm >= 4.5em\n\n      // Min operator\n      lesser <- fourAndHalfEm <? combined\n      require lesser == fourAndHalfEm\n      stdout.println(`Min of 4.5em and 6em: ${lesser}`)","migrationContext":"Java: plain double, manual unit tracking. F#: compile-time units. CSS: declarative only. EK9: freeform unit literals, unit-safe arithmetic returns unset for mismatches, convert() for boundaries.","keywords":["beginner","convert","css","dimension","first","intro","kilometre","mars","measurement","metre","mile","orbiter","pixel","scaling","start","unit"],"primaryTopics":["dimension","measurement","units"],"typicalErrors":[{"error":"E50060","correct":"eightMiles.convert(numberOfKmInMiles)","incorrect":"eightMiles.convertTo(numberOfKmInMiles)","explanation":"EK9 Dimension does not have a getValue() method. Use operators for arithmetic and the $ operator for string representation. Triggers E50060 — method not resolved. See ek9 -h Dimension for the full API."}],"companions":[]}
{"id":37,"category":"Getting Started","question":"How do I work with strings in EK9?","url":"https://ek9.io/qa/QA0037.html","alternatePhrasings":["How does string interpolation work in EK9?","Can I use strings as streams of characters in EK9?","What string methods does EK9 provide?","How do I extract substrings in EK9?"],"answer":"EK9 strings use double quotes for plain text and backticks for interpolation. Strings are streamable as character sequences, so substring extraction and text analysis use the same stream pipeline syntax as collections.\n\nLITERALS AND INTERPOLATION\n  greeting <- \"Hello World\"\n  stdout.println(`Welcome to ${lang} version ${ver}`)\nEscape sequences: \\t \n \\\\ \\\" \\uXXXX.\n\nKEY METHODS\n  .upperCase(), .lowerCase(), .trim(), .length(), .count(char)\n  .rightPadded(n), .leftPadded(n)\nConcatenation with +. Use 'ek9 -h String' for the full API.\n\nSTREAMABLE STRINGS\nStrings stream as characters using skip/head/map/filter/collect:\n  brownFox <- cat sentence | skip 10 | head 9 | collect as String\nThis replaces substring() with unambiguous skip-and-take semantics. Works identically on strings and collections.\n\nComparison: ==, <>, <, >, <=, >=, <=>. Hashcode: #?.\n\nSee Q33 for regex matching. See Q38 for the Character type. See Q43 for backtick escaping. See Q44 for locale formatting. See Q113 for text construct. See Q170 for string length. See Q171 for string contains. See Q242 for conversion operators.","ek9Example":"defines module qa.strings\n\n  defines function\n\n    charToLowerCaseString()\n      -> ch as Character\n      <- result as String: ch? <- $ch.lowerCase() else String()\n\n    stringComparator()\n      ->\n        s1 as String\n        s2 as String\n      <-\n        comparison as Integer: s1 <=> s2\n\n    justStringValue()\n      -> s1 as String\n      <- rtn as String: s1\n\n    isO()\n      -> ch as Character\n      <- found as Integer: ch == 'o' or ch == 'O' <- 1 else Integer()\n\n  defines program\n    StringDemo()\n      stdout <- Stdout()\n\n      // === STRING LITERALS ===\n\n      // Double-quoted strings\n      greeting <- \"Hello World\"\n      stdout.println(greeting)\n\n      // String interpolation with backticks\n      languageName <- \"EK9\"\n      versionNumber <- 1\n      stdout.println(`Welcome to ${languageName} version ${versionNumber}`)\n\n      // Escape sequences: \\t tab, \\n newline, \\\\ backslash, \\\" quote\n      withTab <- \"Column1\\tColumn2\"\n      stdout.println(withTab)\n\n      withNewline <- \"Line1\\nLine2\"\n      stdout.println(withNewline)\n\n      // Unicode escapes for non-ASCII characters\n      danish <- \"\\u00C6\\u00D8\\u00C5\"\n      stdout.println(`Danish letters: ${danish}`)\n\n      // Unset string\n      unsetString <- String()\n      require ~unsetString?\n\n      // === STRING METHODS ===\n\n      sentence <- \"The Quick Brown Fox\"\n\n      // Case conversion\n      stdout.println(`Upper: ${sentence.upperCase()}`)\n      stdout.println(`Lower: ${sentence.lowerCase()}`)\n\n      // Padding\n      stdout.println(`Right padded: [${sentence.rightPadded(30)}]`)\n      stdout.println(`Left padded: [${sentence.leftPadded(30)}]`)\n\n      // Trimming\n      paddedSentence <- sentence.rightPadded(30)\n      stdout.println(`Trimmed: [${paddedSentence.trim()}]`)\n\n      // Length — two equivalent syntaxes\n      stdout.println(`Length (method): ${sentence.length()}`)\n      stdout.println(`Length (operator): ${length sentence}`)\n\n      // Count character occurrences\n      stdout.println(`Count of 'o': ${sentence.count('o')}`)\n\n      // === CONCATENATION ===\n\n      part1 <- \"The Quick Brown Fox\"\n      part2 <- \"Jumps Over The Lazy Dog\"\n      fullSentence <- `${part1} ${part2}`\n      stdout.println(fullSentence)\n\n      // === HASHCODE ===\n\n      hashValue <- #? sentence\n      stdout.println(`Hashcode: ${hashValue}`)\n\n      // === STRINGS ARE STREAMABLE ===\n\n      // Strings stream as character sequences\n      // Extract a substring using skip and head\n      brownFox <- cat fullSentence | skip 10 | head 9 | collect as String\n      stdout.println(`Extracted: [${brownFox}]`)\n\n      // Going beyond string length is safe — returns what is available\n      endPart <- cat fullSentence | skip 35 | head 20 | collect as String\n      stdout.println(`End part: [${endPart}]`)\n\n      // Join a list of strings via stream collect\n      spacer <- \" \"\n      wordParts <- [part1, spacer, part2]\n      rejoined <- cat wordParts | collect as String\n      stdout.println(`Rejoined: ${rejoined}`)\n\n      // Count occurrences of o/O using stream map\n      oCount <- cat fullSentence | map with isO | collect as Integer\n      stdout.println(`Count of o/O: ${oCount}`)\n\n      // Find unique characters used (sorted, lowercase)\n      charsUsed <- cat fullSentence | map with charToLowerCaseString | sort with stringComparator | uniq by justStringValue | collect as String\n      stdout.println(`Unique chars: [${charsUsed}]`)\n\n      // === COMPARISON ===\n\n      alpha <- \"abc\"\n      beta <- \"def\"\n      require alpha < beta\n      require beta > alpha\n      require alpha <> beta\n      require alpha == \"abc\"\n      require alpha <= \"abc\"\n      require alpha >= \"abc\"\n\n      // Min operator\n      fruit1 <- \"apple\"\n      fruit2 <- \"banana\"\n      lesser <- fruit1 <? fruit2\n      require lesser == \"apple\"\n      stdout.println(`Min: ${lesser}`)","migrationContext":"Java: no native interpolation, substring() methods, no streamable characters. Python: f-strings, slicing [start:end]. Kotlin: similar $var templates. EK9: backtick interpolation, strings natively streamable as characters, skip/head replaces substring().","keywords":["backtick","beginner","concatenation","count","escape","first","interpolation","intro","length","lowercase","migrate","pad","start","stream","string","substring","trim","unicode","uppercase"],"primaryTopics":["string","text","string type"],"typicalErrors":[{"error":"E07620","correct":"greeting <- \"Hello World\"","incorrect":"greeting <- \"Hello World\" + 42","explanation":"The + operator on String requires another String operand. Adding an Integer to a String triggers E07620 — type incompatibility. Use $ to convert to string first: \"Hello\" + $number. See ek9 -h E07620 for details."}],"companions":[]}
{"id":38,"category":"Getting Started","question":"How do I work with characters in EK9?","url":"https://ek9.io/qa/QA0038.html","alternatePhrasings":["Does EK9 have a character type separate from strings?","How do I convert a character to a string in EK9?","How does the promote operator work with characters?","How do I iterate through characters in EK9?"],"answer":"EK9 has a dedicated Character type for single Unicode characters, separate from String. Single quotes for characters, double quotes for strings. No implicit conversion — use #^ promote operator.\n\nCHARACTER LITERALS\n  letter <- 'a'\n  accent as Character := '\\u00E9'\nCharacter() creates unset, Character(stringValue) creates from single-char string.\n\nCASE CONVERSION\nInstance methods:\n  upper <- letter.upperCase()\n  lower <- upper.lowerCase()\n  roundTrip <- letter.upperCase().lowerCase()\n\nPROMOTE TO STRING: THE #^ OPERATOR\n  asString <- #^ letter               Prefix syntax\n  alsoString <- letter.#^()            Method call syntax\n  typed as String := #^letter          With explicit type\nAll conversions explicit and visible. $ also converts to string for display, but #^ is the semantic type promotion.\n\nLENGTH\nSet character has length 1, unset has length 0:\n  length letter          returns 1\n  letter.length()        method call syntax\n\nHASHCODE\n  hashValue <- #? letter               Prefix syntax\n  hashValue <- letter.#?()             Method call syntax\n\nCOPY OPERATOR\n  copied <- Character()\n  copied :=: 'z'\n\nINCREMENT AND DECREMENT\n  working <- 'a'\n  working++              now 'b'\n  working--              back to 'a'\n\nCOMPARISON\nBy Unicode code point: ==, <>, <, >, <=, >=, <=>.\n\nTRI-STATE\nComparing with unset returns unset Boolean:\n  result <- Character() == 'a'     result is unset\n\nCHARACTERS AND STRINGS TOGETHER\nStrings are streamable as character sequences (see Q37). #^ converts Characters back to Strings for stream operations.\n\nWHY SEPARATE?\nPython/JavaScript have no character type. EK9 keeps them distinct: Character has case conversion, code-point comparison, ++/--. String has trim(), padded(), count(). The #^ operator makes the boundary explicit.\n\nUse 'ek9 -h Character' for the full API.\n\nSee Q37 for strings. See Q43 for escape and interpolation.","ek9Example":"defines module qa.character\n\n  defines program\n    CharacterDemo()\n      stdout <- Stdout()\n\n      // === CHARACTER LITERALS ===\n\n      // Single-quoted character literals\n      letter <- 'a'\n      digit <- '9'\n      stdout.println(`Letter: ${letter}, Digit: ${digit}`)\n\n      // Unicode escape for non-ASCII\n      accent as Character := '\\u00E9'\n      stdout.println(`Accented: ${accent}`)\n\n      // Unset character\n      unsetChar <- Character()\n      require ~unsetChar?\n      stdout.println(`Unset isSet: ${unsetChar?}`)\n\n      // === CASE CONVERSION ===\n\n      // Instance methods — not static utility calls\n      upper <- letter.upperCase()\n      stdout.println(`Uppercase of ${letter}: ${upper}`)\n\n      lower <- upper.lowerCase()\n      stdout.println(`Lowercase of ${upper}: ${lower}`)\n\n      // Method chaining\n      roundTrip <- letter.upperCase().lowerCase()\n      require roundTrip == letter\n      stdout.println(`Round trip: ${roundTrip}`)\n\n      // === PROMOTE TO STRING ===\n\n      // The #^ operator explicitly converts Character to String\n      asString <- #^ letter\n      stdout.println(`Promoted: ${asString}`)\n\n      // Also available as method call syntax\n      alsoString <- letter.#^()\n      stdout.println(`Method promote: ${alsoString}`)\n\n      // Explicit typing shows the conversion\n      typed as String := #^letter\n      require typed?\n      stdout.println(`Typed promote: ${typed}`)\n\n      // === LENGTH ===\n\n      // A set character always has length 1\n      stdout.println(`Length of '${letter}': ${length letter}`)\n      stdout.println(`Length method: ${letter.length()}`)\n\n      // An unset character has length 0\n      stdout.println(`Unset length: ${length unsetChar}`)\n\n      // === HASHCODE ===\n\n      // Two syntax forms for hashcode\n      hashPrefix <- #? letter\n      hashMethod <- letter.#?()\n      stdout.println(`Hashcode prefix: ${hashPrefix}`)\n      stdout.println(`Hashcode method: ${hashMethod}`)\n\n      // String interpolation with hashcode\n      stdout.println(`Hashcode of ${accent} is ${#?accent}`)\n\n      // === COPY OPERATOR ===\n\n      // :=: deep copies one character to another\n      copied <- Character()\n      copied :=: 'z'\n      require copied == 'z'\n      stdout.println(`Copied: ${copied}`)\n\n      // === INCREMENT AND DECREMENT ===\n\n      // ++ moves to next character, -- to previous\n      working <- 'a'\n      working++\n      require working == 'b'\n      stdout.println(`After ++: ${working}`)\n\n      working--\n      require working == 'a'\n      stdout.println(`After --: ${working}`)\n\n      // === COMPARISON ===\n\n      // Characters compare by Unicode value\n      require 'a' < 'z'\n      require 'z' > 'a'\n      require 'a' <> 'z'\n      require 'a' == 'a'\n      require 'a' <= 'a'\n      require 'a' >= 'a'\n\n      // Comparisons with unset return unset (not true, not false)\n      unsetCompare <- unsetChar == letter\n      require ~unsetCompare?\n      stdout.println(`Unset compare isSet: ${unsetCompare?}`)\n\n      // Spaceship operator for sorting\n      ordering <- letter <=> 'z'\n      stdout.println(`letter <=> 'z': ${ordering}`)","migrationContext":"Java: primitive char (16-bit UTF-16), static Character.toUpperCase(). Python/JS: no character type. Rust: char is full Unicode scalar. EK9: single-quoted literals, full Unicode, instance methods .upperCase()/.lowerCase(), #^ promote to String, ++/-- operators.","keywords":["beginner","char","character","convert","decrement","first","increment","intro","literal","lowercase","migrate","promote","quote","single","start","string","unicode","uppercase"],"primaryTopics":["character","char","character type"],"typicalErrors":[{"error":"E50060","correct":"upper <- letter.upperCase()","incorrect":"upper <- letter.toUpper()","explanation":"EK9 Character uses upperCase() and lowerCase(), not toUpper() or toUpperCase(). Triggers E50060 — method not resolved. See ek9 -h Character for the full API."}],"companions":[]}
{"id":39,"category":"Getting Started","question":"How do Integer and Float work in EK9?","url":"https://ek9.io/qa/QA0039.html","alternatePhrasings":["What numeric types does EK9 have and how do hex and binary literals work?","Why does division by zero return unset instead of throwing an exception?","Why does EK9 only have two numeric types instead of byte, short, long, unsigned?"],"answer":"EK9 has two numeric types: Integer (64-bit signed whole numbers) and Float (64-bit IEEE 754 floating-point). Both are full objects, not primitives.\n\nINTEGER LITERALS\nDecimal: 42, -17, 0\nHex: 0xFF, 0x1F (prefix 0x)\nBinary: 0b1010, 0b11001100 (prefix 0b)\nUnderscores for readability: 1_000_000, 0xFF_FF\nRange: -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807\n\nFLOAT LITERALS\nDecimal: 3.14, -2.5, 0.0\nScientific notation: 4.9E-324, 1.7976931348623157E308\n\nARITHMETIC OPERATORS\nBoth types support: + - * / ^ (power), abs, sqrt\n  17 + 5 = 22,  17 / 5 = 3 (integer division)\n  3.14 * 2.0 = 6.28,  2.0 ^ 0.5 = square root of 2\n\nDIVISION BY ZERO RETURNS UNSET\nThis is one of EK9's most important safety decisions. Java throws ArithmeticException for integer division by zero and produces NaN/Infinity for float. JavaScript silently produces Infinity or NaN that propagates through calculations. EK9 returns unset for ANY division by zero:\n  90 / 0 is unset,  0 / 0 is unset,  90.0 / 0.0 is unset\nBut 0 / 90 = 0 (zero divided by non-zero is valid).\nIEEE 754 NaN was designed to let calculations continue, hoping programmers would check later. 50 years shows they don't. NaN propagates silently producing garbage, and NaN != NaN breaks equality. EK9's unset achieves the same 'continue calculating' benefit but the ? operator makes checking explicit.\n\nPROMOTE INTEGER TO FLOAT: #^\nUse the #^ operator to convert Integer to Float:\n  intVal <- 42\n  floatVal <- #^ intVal\nFloat has no promote because it is already the widest numeric type. This explicit promotion means you always know where Integer becomes Float.\n\nWHY ONLY TWO NUMERIC TYPES?\nJava has 6 numeric primitives plus boxed wrappers. Rust has 12 numeric types. Go has 15+. EK9 has Integer and Float. Period. No byte, short, long, unsigned, int8, float32.\nThis eliminates: silent narrowing conversions (Java long to int), signed/unsigned confusion (C unsigned overflow), float-to-double precision surprises, integer overflow wrapping. One whole number type, one decimal type. If you need arbitrary precision decimals, use Money. If you need bit manipulation, use Bits.\n\nSee Q91 for advanced features (mod vs rem, factorial, bitwise, locale formatting, streams). See Q40 for the Bits type. See Q25 for more on the #^ promote operator. See Q44 for Locale formatting. See Q240 for arithmetic and math operators (mod, rem, abs, sqrt, ^). Use 'ek9 -h Integer' and 'ek9 -h Float' for the full API.","ek9Example":"defines module qa.integer.and.float\n\n  defines program\n    IntegerAndFloatDemo()\n      stdout <- Stdout()\n\n      // === INTEGER LITERALS ===\n\n      decimal <- 42\n      hex <- 0xFF\n      binary <- 0b10101010\n      big <- 1_000_000\n\n      stdout.println(`Decimal: ${decimal}`)\n      stdout.println(`Hex 0xFF: ${hex}`)\n      stdout.println(`Binary 0b10101010: ${binary}`)\n      stdout.println(`With underscores: ${big}`)\n\n      // === BASIC ARITHMETIC ===\n\n      a <- 17\n      b <- 5\n      stdout.println(`${a} + ${b} = ${a + b}`)\n      stdout.println(`${a} - ${b} = ${a - b}`)\n      stdout.println(`${a} * ${b} = ${a * b}`)\n      stdout.println(`${a} / ${b} = ${a / b}`)\n\n      // Power, absolute value, square root\n      stdout.println(`2 ^ 10 = ${2 ^ 10}`)\n      stdout.println(`abs -49 = ${abs -49}`)\n      stdout.println(`sqrt 49 = ${sqrt 49}`)\n\n      // === FLOAT BASICS ===\n\n      pi <- 3.14159\n      scientific <- 2.5E10\n      stdout.println(`Pi: ${pi}`)\n      stdout.println(`Scientific: ${scientific}`)\n      stdout.println(`2.0 ^ 0.5 = ${2.0 ^ 0.5}`)\n      stdout.println(`abs -16.0 = ${abs -16.0}`)\n\n      // === DIVISION BY ZERO RETURNS UNSET ===\n\n      zero <- 0\n      divResult <- 90 / zero\n      stdout.println(`90 / 0 isSet: ${divResult?}`)\n\n      fzero <- 0.0\n      fResult <- 90.0 / fzero\n      stdout.println(`90.0 / 0.0 isSet: ${fResult?}`)\n\n      // Zero divided by non-zero is valid\n      normalDiv <- 0 / 90\n      stdout.println(`0 / 90 = ${normalDiv}`)\n\n      // === PROMOTE INTEGER TO FLOAT ===\n\n      intVal <- 42\n      floatVal <- #^ intVal\n      stdout.println(`Promoted: ${floatVal}`)\n\n      // === COMPARISON ===\n\n      five <- 5\n      anotherFive <- 5\n      ten <- 10\n      stdout.println(`5 < 10: ${five < ten}`)\n      stdout.println(`5 == 5: ${five == anotherFive}`)\n      ordering <- five <=> ten\n      stdout.println(`5 <=> 10: ${ordering}`)","migrationContext":"Java: 6 numeric primitives (byte, short, int, long, float, double) plus boxed wrappers, ArithmeticException on integer div-by-zero, Double.NaN for float div-by-zero. Python: arbitrary precision integers, float backed by C double, ZeroDivisionError. JavaScript: single Number type (IEEE 754 double), Infinity/NaN, no integer type. Rust: 12 numeric types (i8-i128, u8-u128, f32, f64), panic on overflow in debug. Go: 15+ numeric types, panic on integer div-by-zero, math.NaN for float. EK9: Integer (64-bit) and Float (64-bit) only, div-by-zero returns unset, explicit #^ promote.","keywords":["arithmetic","basics","beginner","binary","division","first","float","hex","integer","intro","literal","migrate","nan","number","numeric","promote","start","underscore","unset","zero"],"primaryTopics":["integer","float","number","numeric type"],"typicalErrors":[{"error":"E50030","correct":"decimal <- 42","incorrect":"decimal as Integer: \"forty-two\"","explanation":"A String value cannot be assigned to an Integer variable. EK9 is strongly typed and does not implicitly convert between unrelated types. Use Integer(\"42\") for explicit parsing. See ek9 -h E50030 for details."}],"companions":[]}
{"id":40,"category":"Getting Started","question":"How does the Bits type work in EK9?","url":"https://ek9.io/qa/QA0040.html","alternatePhrasings":["How do I do bit shifting in EK9?","What is the difference between Integer and Bits in EK9?","How do I manipulate individual bits in EK9?","Why does EK9 have a separate Bits type instead of using integers for bit operations?"],"answer":"Bits is EK9's dedicated type for bit-level operations — a variable-length ordered collection of bits, NOT an integer. Binary literals: 0b010011. The + operator CONCATENATES bits.\n\nBitwise: and, or, xor, ~ (not). Shift: << grows (adds zeros), >> shrinks. Integer does NOT have shift operators. Streamable as Booleans (LSB first). Integrates with Colour via .bits() and Colour(bitsValue).\n\nUse 'ek9 -h Bits' to see the full API.\n\nSee Q39 for Integer. See Q34 for Colour-to-Bits integration. See Q244 for bitwise operators.","ek9Example":"defines module qa.bits\n\n  defines function\n\n    booleanToBits()\n      -> b as Boolean\n      <- rtn as Bits: Bits(b)\n\n  defines program\n    BitsDemo()\n      stdout <- Stdout()\n\n      // === BITS LITERALS ===\n\n      // Binary literals with 0b prefix\n      a <- 0b010011\n      b <- 0b101010\n      stdout.println(`a: ${a}`)\n      stdout.println(`b: ${b}`)\n\n      // Unset bits\n      unsetBits <- Bits()\n      require ~unsetBits?\n      stdout.println(`Unset isSet: ${unsetBits?}`)\n\n      // === BITWISE OPERATORS ===\n\n      // and, or, xor\n      anded <- a and b\n      ored <- a or b\n      xored <- a xor b\n      stdout.println(`a and b: ${anded}`)\n      stdout.println(`a or b: ${ored}`)\n      stdout.println(`a xor b: ${xored}`)\n\n      // not operator (two syntaxes)\n      notted <- ~a\n      alsoNotted <- not a\n      stdout.println(`not a (~): ${notted}`)\n      stdout.println(`not a: ${alsoNotted}`)\n\n      // === SHIFT OPERATORS ===\n\n      c <- 0b01010011\n\n      // Shift left adds zeros on right, grows the bit sequence\n      shiftedLeft <- c << 1\n      stdout.println(`c << 1: ${shiftedLeft}`)\n\n      shiftedLeft2 <- c << 2\n      stdout.println(`c << 2: ${shiftedLeft2}`)\n\n      // Shift right removes bits from right\n      shiftedRight <- c >> 2\n      stdout.println(`c >> 2: ${shiftedRight}`)\n\n      // === CONCATENATION WITH + (NOT ADDITION) ===\n\n      // + joins bits together — this is NOT numeric addition\n      set6 <- 0b010011\n      set7 <- 0b101010\n      joined <- set6 + set7\n      stdout.println(`set6 + set7: ${joined}`)\n\n      // Append a Boolean (true=1, false=0)\n      withTrue <- set6 + true\n      stdout.println(`set6 + true: ${withTrue}`)\n\n      withFalse <- set6 + false\n      stdout.println(`set6 + false: ${withFalse}`)\n\n      // Mutating append\n      growing <- 0b11\n      growing += true\n      growing += false\n      stdout.println(`Growing: ${growing}`)\n\n      // === COMPARISON ===\n\n      require a == 0b010011\n      require a <> b\n      require a < b\n      require b > a\n\n      // Spaceship\n      ordering <- a <=> b\n      stdout.println(`a <=> b: ${ordering}`)\n\n      // === LENGTH ===\n\n      stdout.println(`Length of a: ${length a}`)\n      stdout.println(`Length of joined: ${length joined}`)\n\n      // === STREAMING BITS AS BOOLEANS ===\n\n      // Bits are streamable — each element is a Boolean\n      // Least significant bit first (right to left)\n      set6false <- 0b01001101\n\n      partial <- cat set6false | skip 3 | map with booleanToBits | collect as Bits\n      stdout.println(`Skip 3 bits: ${partial}`)\n\n      // === HASHCODE ===\n\n      hash <- #? a\n      stdout.println(`Hash of a: ${hash}`)\n\n      // === COPY ===\n\n      copied <- Bits()\n      copied :=: a\n      require copied == a\n      stdout.println(`Copied: ${copied}`)","migrationContext":"C/Java/Python/JS: bit ops on integers, signed/unsigned confusion, fixed-width. EK9: dedicated variable-length Bits type, + concatenates, << grows, >> shrinks, streamable as Booleans.","keywords":["and","beginner","binary","bits","bitwise","boolean","colour","concatenate","first","flag","hardware","intro","mask","migrate","not","or","pixel","register","shift","start","stream","xor"],"primaryTopics":["bits","bitwise","bit manipulation"],"typicalErrors":[{"error":"E50060","correct":"shiftedLeft <- c << 1","incorrect":"shiftedLeft <- c.shiftLeft(1)","explanation":"EK9 Bits does not have setBit() or getBit() methods. Use bitwise operators: and, or, xor, ~, >>, <<. Triggers E50060 — method not resolved. See ek9 -h Bits for the full API."}],"companions":[]}
{"id":41,"category":"Getting Started","question":"How does the Millisecond type work in EK9 and how does it relate to Duration?","url":"https://ek9.io/qa/QA0041.html","alternatePhrasings":["How do I measure elapsed time in EK9?","What is the ms literal suffix in EK9?","How do I convert between Millisecond and Duration in EK9?","How do I set timeouts in EK9?"],"answer":"Millisecond is EK9's dedicated type for precise timing and performance measurement using the ms suffix: 100ms, 500ms. Type-safe — cannot mix with incompatible types.\n\nArithmetic: +, -, multiply/divide by Integer or Float, Millisecond/Millisecond gives Float ratio. Conversion: .duration() converts to Duration (#^ also promotes). Construct from Duration: Millisecond(P2W).\n\nPerformance timing: SystemClock().millisecond() for start/end measurement.\n\nUse Millisecond for precise timing/timeouts. Use Duration for calendar-scale spans.\n\nUse 'ek9 -h Millisecond' to see the full API.\n\nSee Q32 for Duration. See Q31 for Date/Time. See Q44 for locale formatting.","ek9Example":"defines module qa.millisecond\n\n  defines program\n    MillisecondDemo()\n      stdout <- Stdout()\n\n      // === MILLISECOND LITERALS ===\n\n      // Simple ms suffix on integers\n      shortTimeout <- 100ms\n      halfSecond <- 500ms\n      oneSecond <- 1000ms\n      stdout.println(`Short: ${shortTimeout}`)\n      stdout.println(`Half second: ${halfSecond}`)\n      stdout.println(`One second: ${oneSecond}`)\n\n      // Negative milliseconds\n      negMs <- -250ms\n      stdout.println(`Negative: ${negMs}`)\n\n      // Unset\n      unsetMs <- Millisecond()\n      require ~unsetMs?\n      stdout.println(`Unset isSet: ${unsetMs?}`)\n\n      // === ARITHMETIC ===\n\n      // Add milliseconds together\n      combined <- 100ms + 50ms\n      stdout.println(`100ms + 50ms = ${combined}`)\n\n      // Multiply by Float or Integer (scaling)\n      scaled <- 100ms * 3.5\n      stdout.println(`100ms * 3.5 = ${scaled}`)\n\n      scaledInt <- 100ms * 5\n      stdout.println(`100ms * 5 = ${scaledInt}`)\n\n      // Divide by Integer or Float\n      halved <- 1000ms / 2\n      stdout.println(`1000ms / 2 = ${halved}`)\n\n      // Divide Millisecond by Millisecond gives Float ratio\n      ratio <- 750ms / 250ms\n      stdout.println(`750ms / 250ms = ${ratio}`)\n\n      // Increment and decrement\n      ticker <- 100ms\n      ticker++\n      stdout.println(`After ++: ${ticker}`)\n      ticker--\n      stdout.println(`After --: ${ticker}`)\n\n      // Negation\n      pos <- 500ms\n      neg <- -pos\n      stdout.println(`Negated: ${neg}`)\n\n      // Absolute value\n      absVal <- abs neg\n      require absVal == pos\n      stdout.println(`abs: ${absVal}`)\n\n      // === CONVERSION TO DURATION ===\n\n      // duration() method converts Millisecond to Duration\n      // Values >= 500ms round up to the nearest second\n      fiveSeconds <- 5000ms\n      asDuration <- fiveSeconds.duration()\n      stdout.println(`5000ms as Duration: ${asDuration}`)\n\n      // 501ms rounds up to 1 second\n      nearlyASecond <- 501ms\n      stdout.println(`501ms as Duration: ${nearlyASecond.duration()}`)\n\n      // Negative milliseconds\n      negDuration <- -501ms\n      stdout.println(`-501ms as Duration: ${negDuration.duration()}`)\n\n      // #^ promote operator also converts to Duration\n      promoted <- #^ 3000ms\n      stdout.println(`Promoted to Duration: ${promoted}`)\n\n      // === CONSTRUCT FROM DURATION ===\n\n      // Create Millisecond from a Duration literal\n      twoWeeks <- Millisecond(P2W)\n      stdout.println(`P2W as Millisecond: ${twoWeeks}`)\n\n      fiveMinutes <- Millisecond(PT5M)\n      stdout.println(`PT5M as Millisecond: ${fiveMinutes}`)\n\n      // === ADD DURATION TO MILLISECOND ===\n\n      base <- 5400ms\n      withDuration <- base + PT1S\n      stdout.println(`5400ms + PT1S = ${withDuration}`)\n\n      // === PERFORMANCE TIMING ===\n\n      starting <- SystemClock().millisecond()\n      stdout.println(\"Do some work\")\n      ending <- SystemClock().millisecond()\n      elapsed <- ending - starting\n      stdout.println(`Elapsed: ${elapsed}`)\n\n      // === COMPARISON ===\n\n      require 100ms < 500ms\n      require 500ms > 100ms\n      require 100ms <> 500ms\n      require 100ms == 100ms\n      require 100ms <= 100ms\n      require 100ms >= 100ms\n\n      shortDelay <- 100ms\n      ordering <- shortDelay <=> 500ms\n      stdout.println(`100ms <=> 500ms: ${ordering}`)\n\n      // === UNSET PROPAGATION ===\n\n      unsetResult <- unsetMs + 100ms\n      require ~unsetResult?\n      stdout.println(`Unset + 100ms isSet: ${unsetResult?}`)\n\n      // === HASHCODE ===\n\n      hash <- #? 500ms\n      stdout.println(`Hash of 500ms: ${hash}`)","migrationContext":"Java: System.currentTimeMillis() returns raw long. Python: time.time() returns float. Rust/Go: no ms literal. EK9: native ms literal, dedicated type, promotes to Duration via #^.","keywords":["beginner","clock","convert","delay","duration","elapsed","first","interval","intro","migrate","millisecond","ms","performance","promote","start","stopwatch","timeout","timing"],"primaryTopics":[],"typicalErrors":[{"error":"E50060","correct":"fiveSeconds.duration()","incorrect":"fiveSeconds.toSeconds()","explanation":"EK9 Millisecond does not have toSeconds() or similar conversion methods. Use .duration() to convert to Duration, or use arithmetic operators. Triggers E50060 — method not resolved. See ek9 -h Millisecond for the full API."}],"companions":[]}
{"id":42,"category":"Getting Started","question":"How does the Path type work in EK9 and what is it for?","url":"https://ek9.io/qa/QA0042.html","alternatePhrasings":["What is the $? path literal in EK9?","How do I navigate JSON data structures in EK9?","What is the difference between Path and FileSystemPath in EK9?","How do the $, $$, and $? operators differ in EK9?"],"answer":"IMPORTANT: Path in EK9 is NOT a file system path. It is a data navigation path for traversing object graphs like JSON structures. If you need file system paths, use FileSystemPath instead. This is a critical distinction that developers from other languages often miss.\n\nPATH IS FOR DATA NAVIGATION\nPath uses the $? prefix to create compile-time checked navigation expressions for object graphs (JSON, nested records, data structures):\n  simplePath <- $?.aKey\n  nested <- $?.some.path.to.value\n  fromArray <- $?[0].a-field\n  multiDim <- $?.data[2][1].multi-dimensional.array\n  complex <- $?.some.path.inc[0].array\n\nFILESYSTEMPATH IS FOR FILES\nFileSystemPath is the separate type for file and directory operations:\n  filePath <- FileSystemPath(\"/home/user/file.txt\")\nIt has methods like parent(), fileName(), extension(), exists(). Path has none of these because it navigates data, not file systems.\n\nTHE $? PREFIX\nThe path literal starts with $? followed by property names and array indices:\n  Properties: $?.propertyName (dot-separated)\n  Arrays: $?[index] (bracket notation)\n  Mixed: $?.store.book[0].title\n  Multi-dimensional: $?[2][1].field\n  Hyphenated keys: $?[0].a-field\n\nThe compiler validates the syntax at compile time. Unlike Java's JsonPath strings or Python's dict key chains, typos and structural errors are caught before your code runs.\n\nTHE $ OPERATOR FAMILY\nEK9 has three related operators that all use $ as a visual anchor but serve completely different purposes:\n\n  $value      to-string conversion (any type to String)\n  $$record    to-JSON serialization (record/class to JSON)\n  $?.path     data path literal (compile-time checked navigation)\n\nOne $ is simple conversion, two $$ is structured output, $? is structured input navigation. This visual pattern makes the intent immediately recognizable in code.\n\nESCAPING $ IN INTERPOLATED STRINGS\nSince $ triggers interpolation in backtick strings, use \\$ to include a literal dollar sign:\n  stdout.println(`\\$ to-string: ${stringRep}`)\n  stdout.println(`\\$\\$ to-JSON: ${jsonOfMe}`)\n  stdout.println(`\\$? path: ${navPath}`)\nIn normal double-quoted strings, $ does not need escaping. In backtick interpolation, $ and backtick must be escaped with backslash.\n\nPATH CONCATENATION\nThe + operator joins paths:\n  basePath <- $?.api\n  extended <- basePath + $?.users\nYou can also append strings:\n  withVersion <- basePath + \"/v2\"\n\nPATH OPERATORS\n  contains: check if path contains a substring\n    fullPath contains \"store\"     true/false\n  matches: match against a regular expression\n    path matches /user.*/         true/false\n  length: character count of the path\n  ==, <>, <, >, <=, >=, <=>: comparison operators\n\nWHY A BUILT-IN PATH TYPE?\nModern applications constantly navigate data structures: JSON API responses, configuration trees, nested records. In Java, you use string-based JsonPath (\"$.store.book[0].title\") with no compile-time checking. In JavaScript, optional chaining (obj?.store?.book?.[0]?.title) is concise but still unchecked. In Python, nested dict access (data['store']['book'][0]) throws KeyError at runtime.\n\nEK9 makes data navigation a first-class type. Paths can be stored in variables, passed as parameters, composed programmatically, and validated at compile time. This is the same philosophy as RegEx being built-in rather than a library: if something is used everywhere, it should be part of the language with compile-time safety.\n\nWHY SEPARATE FROM FILESYSTEMPATH?\nBecause they solve completely different problems:\n  Path: navigates IN-MEMORY data structures (JSON, object graphs)\n  FileSystemPath: navigates ON-DISK file system hierarchies\n\nMixing these concepts (as most languages do with a single 'path' type or string) leads to confusion. A JSON path $?.users[0].name has nothing to do with /home/users/name.txt. Different types, different operations, different safety requirements.\n\nSee Q43 (How do I escape characters in EK9 string interpolation?) for the complete rules on escaping $ in backtick strings. See Q37 (How do I work with strings in EK9?) for string interpolation basics.\n\nUse 'ek9 -h Path' and 'ek9 -h FileSystemPath' to see the full APIs.","ek9Example":"defines module qa.path\n\n  defines record\n    CustomerDetail\n      firstName <- String()\n      lastName <- String()\n\n      default CustomerDetail()\n\n      CustomerDetail()\n        ->\n          firstName as String\n          lastName as String\n        this.firstName :=: firstName\n        this.lastName :=: lastName\n\n      default operator $$\n\n      default operator ?\n\n  defines program\n    PathDemo()\n      stdout <- Stdout()\n\n      // === PATH LITERALS ===\n\n      // $? prefix creates a compile-time checked path\n      simplePath <- $?.aKey\n      stdout.println(`Simple: ${simplePath}`)\n\n      // Nested property access\n      nested <- $?.some.path.to.value\n      stdout.println(`Nested: ${nested}`)\n\n      // Array indexing\n      firstElement <- $?[0]\n      stdout.println(`First element: ${firstElement}`)\n\n      // Property from array element\n      fromArray <- $?[0].a-field\n      stdout.println(`From array: ${fromArray}`)\n\n      // Multi-dimensional array access\n      multiDim <- $?.data[2][1].multi-dimensional.array\n      stdout.println(`Multi-dim: ${multiDim}`)\n\n      // Complex path\n      complex <- $?.some.path.inc[0].array\n      stdout.println(`Complex: ${complex}`)\n\n      // === DISTINGUISH FROM $ AND $$ ===\n\n      // $value is the to-string operator\n      anInt <- 42\n      stringRep <- $anInt\n      stdout.println(`\\$ to-string: ${stringRep}`)\n\n      // $$record is the to-JSON operator\n      me <- CustomerDetail(firstName: \"Steve\", lastName: \"Limb\")\n      jsonOfMe <- $$me\n      stdout.println(`\\$\\$ to-JSON: ${jsonOfMe}`)\n\n      // $?.path is the path navigation literal\n      navPath <- $?.store.book[0].title\n      stdout.println(`\\$? path: ${navPath}`)\n\n      // === PATH CONCATENATION ===\n\n      basePath <- $?.api\n      extended <- basePath + $?.users\n      stdout.println(`Concatenated: ${extended}`)\n\n      // Append string\n      withString <- basePath + \"/v2\"\n      stdout.println(`With string: ${withString}`)\n\n      // === PATH OPERATORS ===\n\n      // Length\n      stdout.println(`Length: ${length nested}`)\n\n      // Comparison\n      path1 <- $?.alpha\n      path2 <- $?.beta\n      require path1 <> path2\n      require path1 < path2\n      stdout.println(`alpha <=> beta: ${path1 <=> path2}`)\n\n      // Contains\n      fullPath <- $?.store.book[0].title\n      containsResult <- fullPath contains \"store\"\n      stdout.println(`Contains 'store': ${containsResult}`)\n\n      // Matches with RegEx\n      matchPath <- $?.users\n      matchResult <- matchPath matches /user.*/\n      stdout.println(`Matches /user.*/: ${matchResult}`)\n\n      // === HASHCODE ===\n\n      hash <- #? simplePath\n      stdout.println(`Hash: ${hash}`)\n\n      // === UNSET PATH ===\n\n      unsetPath <- Path()\n      require ~unsetPath?\n      stdout.println(`Unset isSet: ${unsetPath?}`)\n\n      // === COPY ===\n\n      copied <- Path()\n      copied :=: nested\n      require copied == nested\n      stdout.println(`Copied: ${copied}`)","migrationContext":"Java: JsonPath library with string expressions $.store.book[0].title, no compile-time checking, runtime exceptions on invalid paths, java.nio.file.Path for file system. JavaScript: native optional chaining obj?.store?.book?.[0], jq for CLI JSON queries, no compile-time validation, fs.path for files. Python: nested dict access data['store']['book'][0] with KeyError, jsonpath-ng library, pathlib.Path for files. Rust: serde_json Value indexing value[\"store\"][\"book\"][0], jsonpath-rust crate, std::path::Path for files. Go: encoding/json with map[string]interface{} casting, gjson library for JSON paths, filepath package for files. C#: System.Text.Json JsonElement navigation, JsonPath.Net library, System.IO.Path for files. EK9: native $?.path literal with compile-time validation, Path type for data navigation, FileSystemPath type for files, clear separation of concerns.","keywords":["array","beginner","compile","data","dollar","escape","file","filesystempath","first","graph","interpolation","intro","json","literal","migrate","navigation","object","path","property","start"],"primaryTopics":[],"typicalErrors":[{"error":"E08180","correct":"firstName <- String()\n      lastName <- String()","incorrect":"firstName as String\n      lastName as String","explanation":"Record fields must be initialised at declaration. Declaring fields without initialisation triggers E08180. Use inline initialisation like 'String()' to create an unset-but-initialised field. See ek9 -h E08180 for details."}],"companions":[]}
{"id":43,"category":"Getting Started","question":"How do I escape characters in EK9 string interpolation?","url":"https://ek9.io/qa/QA0043.html","alternatePhrasings":["How do I include a literal dollar sign in a backtick string in EK9?","What is the difference between escaping in double-quoted and backtick strings in EK9?","How do I display the $ and $$ operators as text in interpolated strings in EK9?","Why does my backtick string fail when I use $ without interpolation in EK9?"],"answer":"EK9 has two string types with different escaping rules. Double-quoted strings are plain text where $ is just a regular character. Backtick strings support interpolation where ${...} evaluates expressions. This means backtick strings need special escaping for $ and backtick characters that double-quoted strings do not.\n\nBACKTICK STRING ESCAPING (interpolation strings)\nBacktick strings evaluate ${...} expressions. To include literal special characters:\n  \\$    Literal dollar sign (prevents interpolation trigger)\n  \\`    Literal backtick character\n  \\t    Tab\n  \n    Newline\n  \\\\    Literal backslash\n  \\uXXXX  Unicode code point\n\nExamples:\n  salary <- 42000\n  stdout.println(`Your salary is \\$${salary}`)\n  //Output: Your salary is $42000\n\n  stdout.println(`Use \\`backticks\\` for interpolation`)\n  //Output: Use `backticks` for interpolation\n\nDOUBLE-QUOTED STRING ESCAPING (plain text)\nDouble-quoted strings have NO interpolation, so $ is just a regular character:\n  plainDollar <- \"Price: $42\"\n  //Output: Price: $42\n\n  withQuote <- \"She said \\\"hello\\\"\"\n  //Output: She said \"hello\"\n\n  withBackslash <- \"C:\\\\Users\\\\file.txt\"\n  //Output: C:\\Users\\file.txt\n\nWHY THIS MATTERS FOR EK9 OPERATORS\nEK9 uses $ in three operator contexts:\n  $value     to-string conversion\n  $$record   to-JSON serialization\n  $?.path    data navigation path literal\n\nWhen you want to MENTION these operators as text in an interpolated string, you must escape the dollar sign:\n  name <- \"EK9\"\n  stringRep <- $name\n  stdout.println(`\\$ converts to string: ${stringRep}`)\n\n  me <- PersonDetail(name: \"Steve\", age: 30)\n  jsonRep <- $$me\n  stdout.println(`\\$\\$ converts to JSON: ${jsonRep}`)\n\n  navPath <- $?.store.book[0].title\n  stdout.println(`\\$? creates a path: ${navPath}`)\n\nWithout the backslash, the lexer would try to parse $$ or $? as operators inside the string, causing parse errors.\n\nUSING OPERATORS DIRECTLY INSIDE INTERPOLATION\nYou can use $ and $$ operators directly inside ${...} without an intermediate variable:\n  stdout.println(`Direct JSON: ${$$me}`)\n  stdout.println(`Direct to-string: ${$salary}`)\nThe ${$$me} pattern evaluates $$me (to-JSON) and interpolates the result. The ${$salary} pattern evaluates $salary (to-string) and interpolates. This is concise and avoids creating temporary variables just for display.\n\nCOMBINING ESCAPED AND INTERPOLATED CONTENT\nYou can mix escaped dollar signs and interpolation in the same string:\n  price <- 9.99\n  stdout.println(`Item costs \\$${price} (tax not included)`)\n  //Output: Item costs $9.99 (tax not included)\n\nThe \\$ produces a literal dollar sign, then ${price} is the interpolation expression. The lexer processes them left to right: backslash-dollar is a literal, dollar-brace is interpolation.\n\nBACKSLASH-SPACE SHORTCUT\nIf a backslash is followed by a space, it is treated as just a backslash. This is a convenient alternative to \\\\ when the backslash is not immediately before a special character:\n  withBs <- \"path\\ here\"\n  //Output: path\\ here\n\nQUICK REFERENCE TABLE\n  Escape    Double-quoted    Backtick\n  \\$        not needed       REQUIRED (prevents interpolation)\n  \\`        not needed       REQUIRED (prevents string end)\n  \\\"        REQUIRED         not needed\n  \\t        tab              tab\n  \n        newline           newline\n  \\\\        backslash        backslash\n  \\uXXXX   unicode          unicode\n\nThe key insight: double-quoted strings need \\\" for quotes, backtick strings need \\$ for dollars and \\` for backticks. Each string type only needs escaping for its own delimiter and trigger characters.\n\nSee Q37 (How do I work with strings in EK9?) for string methods, streaming, and interpolation basics. See Q42 (How does the Path type work?) for the $? operator that often needs escaping in interpolated output. See Q113 for the text construct for internationalization.\n\nUse 'ek9 -h String' to see the full String API. See Q188 for JSON as first-class type and the $$ operator. See Q242 for conversion operators ($, $$, #?) used in interpolation.","ek9Example":"defines module qa.escape.interpolation\n\n  defines record\n    PersonDetail\n      name <- String()\n      age <- Integer()\n\n      default PersonDetail()\n\n      PersonDetail()\n        ->\n          name as String\n          age as Integer\n        this.name :=: name\n        this.age :=: age\n\n      default operator $$\n\n      default operator ?\n\n  defines program\n    EscapingDemo()\n      stdout <- Stdout()\n\n      // === BACKTICK-SPECIFIC ESCAPING ===\n\n      // \\$ produces a literal dollar sign in backtick strings\n      salary <- 42000\n      stdout.println(`Your salary is \\$${salary}`)\n\n      // Show the $ operator label without triggering interpolation\n      name <- \"EK9\"\n      stringRep <- $name\n      stdout.println(`\\$ converts to string: ${stringRep}`)\n\n      // Show the $$ operator label\n      me <- PersonDetail(name: \"Steve\", age: 30)\n      jsonRep <- $$me\n      stdout.println(`\\$\\$ converts to JSON: ${jsonRep}`)\n\n      // Show the $? path literal label\n      navPath <- $?.store.book[0].title\n      stdout.println(`\\$? creates a path: ${navPath}`)\n\n      // \\` produces a literal backtick inside a backtick string\n      stdout.println(`Use \\`backticks\\` for interpolation`)\n\n      // === DOUBLE-QUOTED STRING ESCAPING ===\n\n      // In double-quoted strings, $ is just a regular character\n      plainDollar <- \"Price: $42\"\n      stdout.println(plainDollar)\n\n      // \\\" for literal quote in double-quoted strings\n      withQuote <- \"She said \\\"hello\\\"\"\n      stdout.println(withQuote)\n\n      // \\\\ for literal backslash\n      withBackslash <- \"C:\\\\Users\\\\file.txt\"\n      stdout.println(withBackslash)\n\n      // === COMMON ESCAPE SEQUENCES (both string types) ===\n\n      // \\t tab, \\n newline\n      withTab <- `Column1\\tColumn2`\n      stdout.println(withTab)\n\n      withNewline <- \"Line1\\nLine2\"\n      stdout.println(withNewline)\n\n      // \\uXXXX for Unicode code points\n      copyright <- \"\\u00A9 2024\"\n      stdout.println(copyright)\n\n      // Backslash followed by space is just a backslash\n      withBs <- \"path\\ here\"\n      stdout.println(withBs)\n\n      // === OPERATORS DIRECTLY IN INTERPOLATION ===\n\n      // Use $$ directly inside ${...} — no intermediate variable needed\n      stdout.println(`Direct JSON: ${$$me}`)\n\n      // Use $ directly inside ${...}\n      stdout.println(`Direct to-string: ${$salary}`)\n\n      // === COMBINING ESCAPING WITH INTERPOLATION ===\n\n      // Mix escaped dollar signs and interpolation in the same string\n      price <- 9.99\n      stdout.println(`Item costs \\$${price} (tax not included)`)\n\n      // Multiple interpolations with escaped operators\n      x <- 42\n      stdout.println(`Integer ${x}, use \\$ for to-string, \\$\\$ for to-JSON`)","migrationContext":"Java: no string interpolation until Java 21 string templates (preview), $ is a regular character in all strings, escape sequences same as EK9 double-quoted strings. Python: f-strings use {expr} for interpolation, literal brace needs {{ doubling not backslash escaping, $ is a regular character. JavaScript: template literals use ${expr} in backticks, literal backtick needs backslash escaping, literal dollar-brace needs \\${ or ${'$'}, very similar rules to EK9 backtick strings. Kotlin: string templates use $var and ${expr}, literal dollar needs ${'$'} trick (no backslash escape), a common pain point. Ruby: string interpolation uses #{expr} in double-quoted strings, $ is a regular character but #{ needs escaping. C#: interpolated strings use $\"...{expr}...\" and literal brace needs {{ doubling. EK9: backtick strings use ${expr}, \\$ for literal dollar, \\` for literal backtick, double-quoted strings are plain text where $ needs no escaping. The backslash-dollar approach is more intuitive than Python's brace doubling or Kotlin's ${'$'} workaround.","keywords":["backslash","backtick","beginner","dollar","escape","first","interpolation","intro","literal","operator","quote","start","string","unicode"],"primaryTopics":["string interpolation","escape character","template string"],"typicalErrors":[{"error":"E08180","correct":"name <- String()\n      age <- Integer()","incorrect":"name as String\n      age as Integer","explanation":"Record fields must be initialised at declaration. Declaring fields without initialisation triggers E08180. Use inline initialisation like 'String()' to create an unset-but-initialised field. See ek9 -h E08180 for details."}],"companions":[]}
{"id":44,"category":"Getting Started","question":"How does the Locale type work in EK9 and how do I format values for different regions?","url":"https://ek9.io/qa/QA0044.html","alternatePhrasings":["How do I format numbers, dates, and currency for different locales in EK9?","What is the difference between shortFormat, mediumFormat, longFormat, and fullFormat in EK9?","How do I display money amounts with locale-specific formatting in EK9?","How does EK9 handle internationalization and localization?"],"answer":"Locale is EK9's unified formatting type for presenting values according to regional conventions. One type handles formatting for Integer, Float, Date, Time, DateTime, Money, Boolean, and Dimension.\n\nCONSTRUCTION\n  enGB <- Locale(\"en_GB\")        single string\n  skSK <- Locale(\"sk\", \"SK\")    language, country\n\nFORMATTING\nUnified API: locale.format(value) for all types. Four named levels for Date/Time/Money: shortFormat(), mediumFormat(), longFormat(), fullFormat(). Day of week: enGB.dayOfWeek(date).\n\nUse 'ek9 -h Locale' to see the full API.\n\nSee Q31 for Date/Time/DateTime. See Q35 for Money. See Q36 for Dimension. See Q37 for string interpolation. See Q39 for Integer/Float formatting.","ek9Example":"defines module qa.locale\n\n  defines program\n    LocaleDemo()\n      stdout <- Stdout()\n\n      // === LOCALE CONSTRUCTION ===\n\n      // Single string with underscore separator\n      enGB <- Locale(\"en_GB\")\n\n      // Dash separator also works\n      enUS <- Locale(\"en-US\")\n\n      // Two-argument form: language, country\n      deutsch <- Locale(\"de_DE\")\n      skSK <- Locale(\"sk\", \"SK\")\n\n      // Access language and country parts\n      stdout.println(`Language: ${enGB.language()}, Country: ${enGB.country()}`)\n\n      // === INTEGER FORMATTING ===\n\n      bigNumber <- 675807\n      negative <- -92208\n\n      // Thousand separators differ by locale\n      stdout.println(`GB: ${enGB.format(bigNumber)}`)\n      stdout.println(`DE: ${deutsch.format(bigNumber)}`)\n      stdout.println(`SK: ${skSK.format(negative)}`)\n\n      // === FLOAT FORMATTING ===\n\n      pi <- 3.14159265358979\n      bigFloat <- -1.797693134862395E12\n\n      // Decimal separators differ by locale\n      stdout.println(`GB pi: ${enGB.format(pi)}`)\n      stdout.println(`DE pi: ${deutsch.format(pi)}`)\n\n      // Control decimal places with second argument\n      stdout.println(`GB pi (2dp): ${enGB.format(pi, 2)}`)\n      stdout.println(`DE big (1dp): ${deutsch.format(bigFloat, 1)}`)\n\n      // === DATE FORMATTING — four named levels ===\n\n      wedding <- 2020-10-03\n\n      stdout.println(`GB short: ${enGB.shortFormat(wedding)}`)\n      stdout.println(`US medium: ${enUS.mediumFormat(wedding)}`)\n      stdout.println(`SK long: ${skSK.longFormat(wedding)}`)\n      stdout.println(`DE full: ${deutsch.fullFormat(wedding)}`)\n\n      // === TIME FORMATTING ===\n\n      lunchTime <- 12:00:01\n\n      stdout.println(`GB short: ${enGB.shortFormat(lunchTime)}`)\n      stdout.println(`DE medium: ${deutsch.mediumFormat(lunchTime)}`)\n\n      // === DATETIME FORMATTING ===\n\n      event <- 2020-10-03T12:00:00Z\n\n      stdout.println(`US short: ${enUS.shortFormat(event)}`)\n      stdout.println(`GB medium: ${enGB.mediumFormat(event)}`)\n      stdout.println(`DE long: ${deutsch.longFormat(event)}`)\n      stdout.println(`SK full: ${skSK.fullFormat(event)}`)\n\n      // === MONEY FORMATTING ===\n\n      tenPounds <- 10#GBP\n      thirtyDollars <- 30.89#USD\n\n      // Default format — symbol + full amount\n      stdout.println(`GB: ${enGB.format(tenPounds)}`)\n      stdout.println(`DE: ${deutsch.format(tenPounds)}`)\n      stdout.println(`SK: ${skSK.format(thirtyDollars)}`)\n\n      // Named levels for money\n      stdout.println(`Medium (no fraction): ${enGB.mediumFormat(tenPounds)}`)\n      stdout.println(`Short (no symbol/fraction): ${enGB.shortFormat(thirtyDollars)}`)\n      stdout.println(`Long (no symbol): ${deutsch.longFormat(tenPounds)}`)\n\n      // Custom control: format(money, withSymbol, withFraction)\n      stdout.println(`Symbol, no fraction: ${enGB.format(arg0: thirtyDollars, showSymbol: true, showFractionalPart: false)}`)\n      stdout.println(`No symbol, with fraction: ${enGB.format(arg0: thirtyDollars, showSymbol: false, showFractionalPart: true)}`)\n\n      // === BOOLEAN FORMATTING ===\n\n      stdout.println(`True in GB: ${enGB.format(true)}`)\n      stdout.println(`True in DE: ${deutsch.format(true)}`)\n\n      // === DIMENSION FORMATTING ===\n\n      dist <- 42.5km\n      stdout.println(`Dimension GB: ${enGB.format(dist)}`)\n      stdout.println(`Dimension GB (1dp): ${enGB.format(dist, 1)}`)\n\n      // === DAY OF WEEK ===\n\n      stdout.println(`Day: ${enGB.dayOfWeek(wedding)}`)\n      stdout.println(`Tag: ${deutsch.dayOfWeek(wedding)}`)\n\n      // === LOCALE OPERATORS ===\n\n      // Comparison\n      require enGB <> deutsch\n      require enGB < enUS\n      stdout.println(`GB <=> US: ${enGB <=> enUS}`)\n\n      // Hashcode\n      stdout.println(`Hash: ${#? enGB}`)\n\n      // To-string and to-JSON\n      stdout.println(`String: ${enGB}`)\n      stdout.println(`JSON: ${$enGB}`)\n\n      // Copy\n      copied <- Locale()\n      copied :=: enGB\n      require copied == enGB\n\n      // Unset\n      unsetLocale <- Locale()\n      require ~unsetLocale?\n      stdout.println(`Unset isSet: ${unsetLocale?}`)\n\n      // Matches\n      matchResult <- enGB matches /en.*/\n      stdout.println(`Matches /en.*/: ${matchResult}`)\n\n      // === USING LOCALE WITH INTERPOLATION ===\n\n      // Locale formatting combines naturally with string interpolation\n      stdout.println(`The wedding was on ${enGB.longFormat(wedding)} at ${enGB.shortFormat(lunchTime)}`)\n      stdout.println(`Cost: ${enGB.format(tenPounds)} (${deutsch.format(tenPounds)} in German format)`)","migrationContext":"Java: separate NumberFormat/DateTimeFormatter/Currency classes. Python: locale module with global state. Rust/Go: no built-in locale. EK9: unified locale.format(value) for all types, named format levels.","keywords":["beginner","country","currency","date","first","format","fullFormat","internationalization","intro","language","locale","localization","longFormat","mediumFormat","migrate","money","number","region","shortFormat","start","time"],"primaryTopics":["locale","localization"],"typicalErrors":[{"error":"E50060","correct":"enGB.language()","incorrect":"enGB.getLanguage()","explanation":"EK9 Locale uses short method names: language(), country() not getLanguage(), getCountry(). Triggers E50060 — method not resolved. See ek9 -h Locale for the full API."},{"error":"E50060","correct":"enGB.country()","incorrect":"enGB.getCountry()","explanation":"EK9 Locale uses short method names: language(), country() not getLanguage(), getCountry(). Triggers E50060 — method not resolved. See ek9 -h Locale for the full API."}],"companions":[]}
{"id":45,"category":"Getting Started","question":"How does the List type work in EK9?","url":"https://ek9.io/qa/QA0045.html","alternatePhrasings":["How do I create and use lists in EK9?","What is the literal syntax for lists in EK9?","How do I add and remove items from a list in EK9?"],"answer":"EK9 has a generic List type for ordered, indexable collections. Lists use square bracket literal syntax [1, 2, 3] and the generic declaration List of T.\n\nLIST CREATION\nFour ways to create lists:\n  numbers <- [1, 2, 3, 4, 5]      literal syntax, compiler infers List of Integer\n  emptyList <- List() of String    typed empty list\n  single <- List(42)               single-element construction\n  fruits <- [\n    \"apple\",\n    \"banana\"\n    ]                              multi-line literal for readability\n\nGENERIC SYNTAX\nEK9 uses 'of' instead of angle brackets: List of String, List of Integer. No <> needed.\n\nADDING ELEMENTS\n+= mutates the list in place:\n  names += \"Dave\"             appends Dave\n  names += [\"Eve\", \"Frank\"]   appends both\n+ creates a new list (original unchanged):\n  moreNames <- names + \"Grace\"\n\nREMOVING ELEMENTS\n-= mutates the list:\n  fruits -= \"banana\"          removes banana\n- creates a new list without the element:\n  without <- fruits - \"banana\"\n\nACCESSING ELEMENTS\nSafe element access with getOrDefault:\n  first <- numbers.getOrDefault(0, 0)                   first element\n  last <- numbers.getOrDefault(length numbers - 1, 0)   last element\ngetOrDefault always returns a value: either the element at the index, or the provided default. No exceptions, no unset results.\n\nLENGTH AND EMPTY CHECK\n  len <- length numbers        number of elements\n  isEmpty <- numbers is empty  true if no elements\n\nITERATION\n  for fruit in fruits\n    stdout.println(fruit)\n\nEMPTY LIST IS SET\nCRITICAL: An empty list IS set. Creating List() of String gives you a valid, set, empty list. Empty is not the same as unset.\n\nSee Q88 for operations (contains, reverse, copy, merge, comparison). See Q89 for stream pipelines (filter, map, collect). See Q37 for string interpolation with lists. See Q39 for integers used in list indexing. See Q120 for sorting lists. See Q125 for head/tail/skip to limit streams. See Q130 for mutating vs non-mutating operators. See Q131 for Python comprehension equivalents. See Q180 for list add. See Q183 for list first and last.","ek9Example":"defines module qa.list\n\n  defines function\n\n    isEven() as pure\n      -> num as Integer\n      <- rtn as Boolean: num mod 2 == 0\n\n    intToString() as pure\n      -> num as Integer\n      <- rtn as String: $num\n\n  defines program\n    ListDemo()\n      stdout <- Stdout()\n\n      // === LIST CREATION ===\n\n      // Literal syntax — compiler infers List of Integer\n      numbers <- [1, 2, 3, 4, 5]\n      stdout.println(`Numbers: ${numbers}`)\n\n      // Typed empty list\n      emptyList <- List() of String\n      stdout.println(`Empty: ${emptyList}`)\n\n      // Single-element construction\n      single <- List(42)\n      stdout.println(`Single: ${single}`)\n\n      // Multi-line literal\n      fruits <- [\n        \"apple\",\n        \"banana\",\n        \"cherry\"\n        ]\n      stdout.println(`Fruits: ${fruits}`)\n\n      // === ADDING ELEMENTS ===\n\n      names <- List() of String\n      names += \"Alice\"\n      names += \"Bob\"\n      names += \"Charlie\"\n      stdout.println(`After +=: ${names}`)\n\n      // + creates a new list (original unchanged)\n      moreNames <- names + \"Dave\"\n      stdout.println(`Original: ${names}`)\n      stdout.println(`With Dave: ${moreNames}`)\n\n      // === REMOVING ELEMENTS ===\n\n      without <- fruits - \"banana\"\n      stdout.println(`Without banana: ${without}`)\n\n      fruits -= \"cherry\"\n      stdout.println(`After -=: ${fruits}`)\n\n      // === ACCESSING ELEMENTS ===\n\n      first <- numbers.getOrDefault(0, 0)\n      last <- numbers.getOrDefault(length numbers - 1, 0)\n      stdout.println(`First: ${first}, Last: ${last}`)\n\n      third <- numbers.getOrDefault(2, 0)\n      stdout.println(`Index 2: ${third}`)\n\n      // === LENGTH AND EMPTY ===\n\n      lenNumbers <- length numbers\n      stdout.println(`Length: ${lenNumbers}`)\n\n      numbersEmpty <- numbers is empty\n      stdout.println(`Numbers empty: ${numbersEmpty}`)\n\n      listEmpty <- emptyList is empty\n      stdout.println(`Empty list empty: ${listEmpty}`)\n\n      // === BASIC ITERATION ===\n\n      for fruit in fruits\n        stdout.println(`Fruit: ${fruit}`)\n\n      // === EMPTY LIST IS SET ===\n\n      require emptyList?\n      require emptyList is empty\n      stdout.println(`Empty list isSet: ${emptyList?}`)","migrationContext":"Java: ArrayList<T> with verbose generics, no literal syntax (List.of() since Java 9 but immutable). Python: built-in list with [] literal, no static type safety. JavaScript: Array with [] literal, no type safety. Rust: Vec<T> with vec![] macro. Go: slices with make()/append(), no generics until Go 1.18. Kotlin: listOf()/mutableListOf(). EK9: [1, 2, 3] literal, List of T generic (no angle brackets), getOrDefault for safe access, += and + for adding, -= and - for removing.","keywords":["add","array","beginner","collection","empty","first","for","generic","getOrDefault","intro","iterate","length","list","literal","migrate","remove","start"],"primaryTopics":["list","array","list type","ordered collection"],"typicalErrors":[{"error":"E06020","correct":"numbers <- [1, 2, 3, 4, 5]","incorrect":"numbers <- List(1, 2, 3, 4, 5)","explanation":"Generic types like List only take the number of parameters that actually parameterize them"},{"error":"E06010","correct":"emptyList <- List() of String","incorrect":"emptyList <- List()","explanation":"The generic type must be parameterized with a type"}],"companions":[]}
{"id":46,"category":"Getting Started","question":"How does the Dict type work in EK9?","url":"https://ek9.io/qa/QA0046.html","alternatePhrasings":["How do I create and use dictionaries in EK9?","How do I look up values safely in an EK9 Dict?","How do I iterate over dictionary entries in EK9?"],"answer":"EK9 has a generic Dict type for key-value mappings. Dicts use curly brace literal syntax {key: value} and the generic declaration Dict of (K, V).\n\nDICT CREATION\nThree ways to create dicts:\n\n1. Literal syntax (compiler infers the types):\n  ages <- {\"Alice\": 30, \"Bob\": 25, \"Charlie\": 35}\nThe compiler infers Dict of (String, Integer) from the literal contents.\n\n2. Empty typed dict:\n  scores <- Dict() of (String, Integer)\nYou must specify both key and value types when creating an empty dict because there are no entries to infer from.\n\n3. Single-entry construction:\n  single <- Dict(\"Alice\", 30)\nCreates a Dict of (String, Integer) with one entry.\n\nTWO-PARAMETER GENERIC SYNTAX\nDict requires two type parameters in parentheses: Dict of (K, V). Compare with List which uses a single parameter: List of T. The parentheses are required for multi-parameter generics.\n\nDICTENTRY BASICS\nDictEntry of (K, V) represents a single key-value pair. Create with DictEntry(key, value). Access the key with .key() and the value with .value(). DictEntry is the unit of insertion and is yielded when iterating over a Dict.\n\nADDING ENTRIES\n+= mutates the dict in place:\n  ages += DictEntry(\"Dave\", 28)\n+ creates a new dict (original unchanged):\n  moreAges <- ages + DictEntry(\"Eve\", 32)\n\nREMOVING ENTRIES\n-= removes by key:\n  ages -= \"Bob\"\n\nSAFE VALUE ACCESS\n.getOrDefault(key, default) returns the default if the key is missing:\n  aliceAge <- ages.getOrDefault(\"Alice\", 0)       returns 30\n  missing <- ages.getOrDefault(\"Unknown\", 0)      returns 0\nAlways returns a usable value. No exceptions, no unset results.\n\nLENGTH AND EMPTY CHECK\n  len <- length ages              number of entries\n  isEmpty <- ages is empty        true if no entries\n\nITERATION\nA for loop over a Dict yields DictEntry values:\n  for entry in ages\n    stdout.println(`${entry.key()}: ${entry.value()}`)\nYou can also use a helper function to format each entry.\n\nSee Q90 (What operations does Dict support?) for contains, keys/values iterators, merging, copy, comparison, JSON, and stream pipelines. See Q45 (How does the List type work?) for the sister collection type. See Q29 (How do unset variables work?) for tri-state semantics and why empty dicts are set. See Q129 for safe access patterns with missing keys. See Q126 for choosing the right collection type. See Q167 for dict iteration.\n\nSee Q260 for Dict key type requirements and using custom types as keys.\n\nUse 'ek9 -h Dict' and 'ek9 -h DictEntry' to see the full API.","ek9Example":"defines module qa.dict\n\n  defines function\n\n    entryToString() as pure\n      -> entry as DictEntry of (String, Integer)\n      <- rtn as String: `${entry.key()}: ${entry.value()}`\n\n  defines program\n    DictDemo()\n      stdout <- Stdout()\n\n      // === DICT CREATION ===\n\n      // Literal syntax — compiler infers Dict of (String, Integer)\n      ages <- {\"Alice\": 30, \"Bob\": 25, \"Charlie\": 35}\n      stdout.println(`Ages: ${ages}`)\n\n      // Typed empty dict\n      scores <- Dict() of (String, Integer)\n      stdout.println(`Empty: ${scores}`)\n\n      // Single-entry construction\n      single <- Dict(\"Alice\", 30)\n      stdout.println(`Single: ${single}`)\n\n      // === DICTENTRY BASICS ===\n\n      entry <- DictEntry(\"Dave\", 28)\n      stdout.println(`Entry key: ${entry.key()}, value: ${entry.value()}`)\n\n      // === ADDING ENTRIES ===\n\n      // += mutates the dict\n      ages += DictEntry(\"Dave\", 28)\n      stdout.println(`After += Dave: ${ages}`)\n\n      // + creates a new dict (original unchanged)\n      moreAges <- ages + DictEntry(\"Eve\", 32)\n      stdout.println(`Original: ${ages}`)\n      stdout.println(`With Eve: ${moreAges}`)\n\n      // === REMOVING ENTRIES ===\n\n      // -= removes by key\n      ages -= \"Bob\"\n      stdout.println(`After -= Bob: ${ages}`)\n\n      // === SAFE VALUE ACCESS ===\n\n      aliceAge <- ages.getOrDefault(\"Alice\", 0)\n      stdout.println(`Alice age: ${aliceAge}`)\n\n      missingAge <- ages.getOrDefault(\"Unknown\", 0)\n      stdout.println(`Missing age (default 0): ${missingAge}`)\n\n      // === LENGTH AND EMPTY CHECK ===\n\n      lenAges <- length ages\n      stdout.println(`Length: ${lenAges}`)\n\n      agesEmpty <- ages is empty\n      stdout.println(`Ages empty: ${agesEmpty}`)\n\n      scoresEmpty <- scores is empty\n      stdout.println(`Scores empty: ${scoresEmpty}`)\n\n      // === ITERATION ===\n\n      // For loop yields DictEntry\n      for ageEntry in ages\n        stdout.println(`${ageEntry.key()} -> ${ageEntry.value()}`)\n\n      // Using a helper function for formatting\n      for ageEntry in ages\n        stdout.println(entryToString(ageEntry))","migrationContext":"Java: HashMap with .get() returning null (NPE risk), no literal syntax. Python: built-in dict with {} literal, .get(key, default). JavaScript: Object/Map, optional chaining. Rust: HashMap, .get() returns Option. Go: map with comma-ok idiom, nil map panics on write. Kotlin: mapOf()/mutableMapOf(), closest in convenience. EK9: {key: value} literal, Dict of (K, V), .getOrDefault() always returns a value, DictEntry for iteration.","keywords":["DictEntry","add","beginner","dict","dictionary","entry","first","generic","getOrDefault","intro","iterate","key","literal","lookup","map","remove","start"],"primaryTopics":["dict","dictionary","map","hashmap","key value"],"typicalErrors":[{"error":"E50010","correct":"scores <- Dict() of (String, Integer)","incorrect":"scores <- HashMap() of (String, Integer)","explanation":"EK9 uses 'Dict' not 'HashMap' or 'Map'. There is no HashMap type in EK9. See ek9 -h E50010 for details."},{"error":"E06010","correct":"single <- Dict(\"Alice\", 30)","incorrect":"single <- Dict()","explanation":"Dict is a generic type requiring two type parameters. An empty Dict() without 'of (K, V)' cannot be resolved. See ek9 -h E06010 for details."},{"error":"E50060","correct":"aliceAge <- ages.getOrDefault(\"Alice\", 0)","incorrect":"aliceAge <- ages.get(\"Alice\", 0)","explanation":"EK9 Dict does not have a '.get()' method. Use '.getOrDefault(key, default)' which always returns a usable value. See ek9 -h E50060 for details."},{"error":"E50001","correct":"lenAges <- length ages","incorrect":"lenAges <- len(ages)","explanation":"EK9 uses 'length' as a prefix operator, not Python's 'len()' function. Write 'length ages' not 'len(ages)'. See ek9 -h E50001 for details."}],"companions":[]}
{"id":47,"category":"Getting Started","question":"How does the Optional type work in EK9?","url":"https://ek9.io/qa/QA0047.html","alternatePhrasings":["How do I handle missing values safely in EK9?","How does EK9 prevent null pointer exceptions?","What is Optional in EK9?","What replaces null in EK9?","What is the EK9 equivalent of Rust Option?"],"answer":"EK9 has a generic Optional type that replaces null with compiler-enforced safety. The compiler tracks whether the '?' (isSet) operator has been called and REFUSES to compile code that accesses an Optional without a proper guard. There are no escape hatches: no .unwrap(), no !!, no force-unwrap.\n\nCREATING OPTIONALS\nTwo forms:\n  maybe <- Optional() of Integer      empty, must specify type\n  item <- Optional(42)                with value, type inferred\n\nCHECKING WITH ?\nThe ? operator checks if an Optional has a value:\n  if item?\n    value <- item.get()               safe: ? in same if block\nThe verbose form 'item is not empty' also works but ? is preferred.\n\nDECLARATION GUARD\nCombine creation with safety check in one expression:\n  if o <- getOptional()\n    value <- o.get()                  safe: declaration implies ? check\nThe variable 'o' only exists inside the if block. If unset, the block is skipped entirely.\n\nGET OR DEFAULT\nExtract the value with a fallback:\n  value <- o.getOrDefault(\"default\")\nReturns the contained value if set, otherwise returns the default. No guard needed.\n\nTHINK EK9 — NOT JAVA, NOT RUST\n  WRONG (Java):    if (opt.isPresent()) opt.get()\n  WRONG (Kotlin):  opt?.let { use(it) } ?: default()\n  WRONG (Rust):    match opt { Some(v) => v, None => default }\n  RIGHT (EK9):     if opt? then use(opt.get())\n\nSee Q84 for Optional operations (comparison, copy, merge, ternary guard). See Q85 for Optional in stream pipelines (flatten, collect). See Q74-Q78 for guard patterns in all flow controls (if, switch, for, while, try). See Q48 for the Result type (success-or-error container). See Q29 for tri-state semantics. See Q54 for Consumer and Acceptor callbacks. See Q126 for choosing the right collection type (List, Dict, Optional, Result, PriorityQueue). See Q198 for built-in generics. See Q243 for coalescing operators (??, ?:) that work with Optional values.\n\nUse 'ek9 -h Optional' to see the full API. See Q251 for fixing unset variable compiler errors. See Q316 for how discarded Optional returns are compiler errors. See Q634 for guard-based safe access. See Q636 for chained guard access. See Q747 for migrating Swift optional binding chains to EK9.","ek9Example":"defines module qa.optional\n\n  defines function\n\n    <?-\n      Returns an Optional with a value.\n    -?>\n    getOptional()\n      <- rtn <- Optional(\"Steve\")\n\n    <?-\n      Returns an empty Optional.\n    -?>\n    getEmptyOptional()\n      <- rtn <- Optional() of String\n\n  defines program\n    OptionalBasics()\n      stdout <- Stdout()\n\n      // === CREATING OPTIONALS ===\n\n      // Empty — must specify type\n      maybe <- Optional() of Integer\n      stdout.println(`Empty isSet: ${maybe?}`)\n\n      // With value — type inferred\n      item <- Optional(42)\n      stdout.println(`Item: ${item}, isSet: ${item?}`)\n\n      // === DECLARATION GUARD ===\n\n      // Preferred: combines creation + safety check\n      if o <- getOptional()\n        extracted <- o.get()\n        stdout.println(`Guard: ${extracted}`)\n\n      // Empty Optional — guard block not entered\n      if empty <- getEmptyOptional()\n        stdout.println(\"Should not print\")\n\n      // Explicit ? check form\n      o2 <- getOptional()\n      if o2?\n        checked <- o2.get()\n        stdout.println(`Checked: ${checked}`)\n\n      // === GET OR DEFAULT ===\n\n      name <- getOptional()\n      assured <- name.getOrDefault(\"Default\")\n      stdout.println(`GetOrDefault (set): ${assured}`)\n\n      emptyName <- getEmptyOptional()\n      defaulted <- emptyName.getOrDefault(\"Fallback\")\n      stdout.println(`GetOrDefault (empty): ${defaulted}`)","migrationContext":"Java: Optional<T> with .get() that throws NoSuchElementException if empty, .isPresent() check not enforced by compiler, .orElse() for defaults. Python: no Optional type, uses None with 'is None' checks, no compile-time safety. Rust: Option<T> with .unwrap() that panics if None, pattern matching for safe access, compiler enforces match exhaustiveness but allows .unwrap() escape hatch. Go: no Optional, uses comma-ok idiom and nil checks. Kotlin: nullable types T? with safe call ?. and Elvis ?:, !! force-unwrap throws NPE. Swift: T? optional with if let/guard let for safe unwrapping, ?? nil coalescing for defaults, ! force-unwrap crashes at runtime (escape hatch EK9 does not allow), optional chaining with ?. for method calls. EK9: Optional of T with compiler-enforced guard patterns, .get() requires ? check in same control structure, no escape hatches, ZERO runtime crashes from Optional misuse.","keywords":["absent","beginner","compiler","empty","enforce","first","get","getOrDefault","guard","intro","isSet","isset","migrate","nil","none","npe","null","null-safe","nullable","optional","replace","safe","safety","set","start","swift","unset"],"primaryTopics":["optional","nullable","maybe","optional type"],"typicalErrors":[{"error":"E08030","correct":"if o2?\n        checked <- o2.get()\n        stdout.println(`Checked: ${checked}`)","incorrect":"checked <- o2.get()\n      stdout.println(`Checked: ${checked}`)","explanation":"Calling .get() on an Optional without first checking with ? triggers E08030 — has not been checked before access. The compiler enforces guard-before-access with no escape hatches. See ek9 -h E08030 for details."},{"error":"E11052","correct":"name <- getOptional()\n      assured <- name.getOrDefault(\"Default\")","incorrect":"getOptional()\n      assured <- getOptional().getOrDefault(\"Default\")","explanation":"Calling a function that returns Optional but discarding the result triggers E11052 — Result or Optional result is discarded and must always be checked. You must always handle the return value. See ek9 -h E11052 for details."}],"companions":[]}
{"id":48,"category":"Getting Started","question":"How does the Result type work in EK9?","url":"https://ek9.io/qa/QA0048.html","alternatePhrasings":["How do I handle success and error values in EK9?","What is the difference between Result and Optional in EK9?","How do I safely access ok and error values from a Result?","How do I handle errors without exceptions in EK9?","What do I use instead of try-catch in EK9?","How do I handle a function that might fail in EK9?"],"answer":"Result of (O, E) is a generic type for operations producing a success value, error value, or BOTH. Unlike Rust's either/or Result, EK9's can hold both simultaneously.\n\nFOUR POSSIBLE STATES\n1. Neither ok nor error: Result() of (String, Integer)\n2. Ok only: Result(\"Steve\", Integer())\n3. Error only: Result(String(), -1)\n4. Both ok AND error: Result(\"Default\", -1)\nThe fourth state handles cases like a failed config lookup that still returns a fallback with an error code.\n\nRESULT CREATION\n  r1 <- Result(\"Steve\", Integer())            two-arg, ok only\n  r2 <- Result() of (String, Integer)          empty\n  r3 <- (Result() of (String, Integer)).asOk(\"Steve\")   factory\n  r4 <- (Result() of (String, Integer)).asError(-1)      factory\n\nCHECKING STATE\n  if r?                checks isOk\n  if r.isOk()          explicit ok check\n  if r.isError()       explicit error check\n  r is empty           neither ok nor error\nisOk() and isError() are INDEPENDENT — both can be true.\n\nBASIC GUARD PATTERNS\nCompiler enforces guard-before-access:\n  if r <- getResult()\n    okData <- r.ok()\n  if r.isError()\n    errData <- r.error()\n\nOK OR DEFAULT / ERROR OR DEFAULT\n  assured <- r.okOrDefault(\"fallback\")\n  errVal <- r.errorOrDefault(0)\n\nSee Q86 for advanced guard patterns. See Q87 for Result operations (whenOk/whenError, merge, copy, comparison, iterator, contains). See Q47 for Optional. See Q29 for tri-state semantics. See Q54 for Consumer/Acceptor. See Q126 for choosing collection types.\n\nUse 'ek9 -h Result' for the full API.\n\nSee Q139 for try/catch vs Result. See Q243 for coalescing operators. See Q251 for unset variable errors. See Q259 for four-state Result design. See Q316 for discarded Result errors.","ek9Example":"defines module qa.result\n\n  defines function\n\n    <?-\n      Returns a Result with ok value set.\n    -?>\n    getOkResult()\n      <- rtn <- Result(\"Steve\", Integer())\n\n    <?-\n      Returns a Result with error value set.\n    -?>\n    getErrorResult()\n      <- rtn <- Result(String(), -1)\n\n    <?-\n      Returns a Result with both ok and error set.\n    -?>\n    getBothResult()\n      <- rtn <- Result(\"Default\", -1)\n\n    <?-\n      Returns an empty Result.\n    -?>\n    getEmptyResult()\n      <- rtn <- Result() of (String, Integer)\n\n  defines program\n    ResultBasics()\n      stdout <- Stdout()\n\n      // === FOUR STATES ===\n\n      okResult <- Result(\"Steve\", Integer())\n      errorResult <- Result(String(), -1)\n      bothResult <- Result(\"Default\", -1)\n      emptyResult <- Result() of (String, Integer)\n\n      stdout.println(`Ok result: ${okResult}`)\n      stdout.println(`Error result: ${errorResult}`)\n      stdout.println(`Both result: ${bothResult}`)\n      stdout.println(`Empty result: ${emptyResult}`)\n\n      // === CHECKING STATE ===\n\n      // ? checks isOk, isOk/isError are independent\n      stdout.println(`okResult?: ${okResult?}, isOk: ${okResult.isOk()}, isError: ${okResult.isError()}`)\n      stdout.println(`errorResult?: ${errorResult?}, isOk: ${errorResult.isOk()}, isError: ${errorResult.isError()}`)\n      stdout.println(`bothResult?: ${bothResult?}, isOk: ${bothResult.isOk()}, isError: ${bothResult.isError()}`)\n      stdout.println(`emptyResult is empty: ${emptyResult is empty}`)\n\n      // === CREATION WITH FACTORY METHODS ===\n\n      factoryOk <- (Result() of (String, Integer)).asOk(\"Alice\")\n      factoryErr <- (Result() of (String, Integer)).asError(99)\n      stdout.println(`Factory ok: ${factoryOk}`)\n      stdout.println(`Factory error: ${factoryErr}`)\n\n      // === BASIC GUARD PATTERNS ===\n\n      // Declaration guard — ? check implied\n      if r <- getOkResult()\n        okData <- r.ok()\n        stdout.println(`Declaration guard: ${okData}`)\n\n      // Explicit isOk check\n      r1 <- getOkResult()\n      if r1.isOk()\n        stdout.println(`isOk guard: ${r1.ok()}`)\n\n      // Explicit isError check\n      r2 <- getErrorResult()\n      if r2.isError()\n        stdout.println(`isError guard: ${r2.error()}`)\n\n      // Dual guard — both ok and error\n      r3 <- getBothResult()\n      if r3.isOk()\n        stdout.println(`Both ok side: ${r3.ok()}`)\n      if r3.isError()\n        stdout.println(`Both error side: ${r3.error()}`)\n\n      // Empty result — neither guard enters\n      if r4 <- getEmptyResult()\n        stdout.println(\"Should not print\")\n\n      // === OK OR DEFAULT / ERROR OR DEFAULT ===\n\n      r5 <- getOkResult()\n      assured <- r5.okOrDefault(\"Fallback\")\n      stdout.println(`okOrDefault (set): ${assured}`)\n\n      r6 <- getEmptyResult()\n      defaulted <- r6.okOrDefault(\"Fallback\")\n      stdout.println(`okOrDefault (empty): ${defaulted}`)\n\n      r7 <- getErrorResult()\n      errVal <- r7.errorOrDefault(0)\n      stdout.println(`errorOrDefault (set): ${errVal}`)\n\n      r8 <- getEmptyResult()\n      errDefault <- r8.errorOrDefault(0)\n      stdout.println(`errorOrDefault (empty): ${errDefault}`)","migrationContext":"Java: no Result, try-catch. Rust: Result<T,E> either/or only, .unwrap() panics. Go: (value, error) tuple, no enforcement. Kotlin/Swift: Result exists but no compiler-enforced access. EK9: four states (both ok AND error), compiler-enforced guards, okOrDefault/errorOrDefault.","keywords":["beginner","catch","compiler","error","errorOrDefault","exception","fail","failure","first","guard","handle","intro","isError","isOk","isset","migrate","null-safe","ok","okOrDefault","result","safe","start","success","swift","try","type"],"primaryTopics":["result","result type","error or value"],"typicalErrors":[{"error":"E08030","correct":"if r1.isOk()\n        stdout.println(`isOk guard: ${r1.ok()}`)","incorrect":"stdout.println(`isOk guard: ${r1.ok()}`)","explanation":"Calling .ok() on a Result without first checking isOk() triggers E08030 — has not been checked before access. The compiler enforces guard-before-access for both .ok() and .error(). See ek9 -h E08030 for details."},{"error":"E06190","correct":"okResult <- Result(\"Steve\", Integer())","incorrect":"okResult <- Result(\"Steve\", String())","explanation":"Result requires two different types for ok and error values. Using the same type for both triggers E06190 — Result must be used with two different types. This ensures the compiler can distinguish ok from error. See ek9 -h E06190 for details."},{"error":"E11052","correct":"r5 <- getOkResult()\n      assured <- r5.okOrDefault(\"Fallback\")","incorrect":"getOkResult()\n      assured <- getOkResult().okOrDefault(\"Fallback\")","explanation":"Calling a function that returns Result but discarding the result triggers E11052 — Result or Optional result is discarded and must always be checked. Always assign and check Results. See ek9 -h E11052 for details."}],"companions":[]}
{"id":49,"category":"Getting Started","question":"How do I define a function in EK9?","url":"https://ek9.io/qa/QA0049.html","alternatePhrasings":["What is a function in EK9?","How do EK9 functions differ from functions in other languages?","How do I create a named function in EK9?"],"answer":"EK9 functions are fundamentally different from functions in other languages because they are first-class TYPES, not just callable code blocks. Every function in EK9 is a nominal type with identity, and functions can participate in type hierarchies via 'is' and 'extends'. This makes them far more powerful than functions in Java, Python, Go, or even Rust.\n\nFunctions are defined inside a 'defines function' block within a module. Parameters use '->' for inputs and '<-' for the named return value:\n  greet()\n    -> name as String\n    <- message as String: \"Hello, \" + name\n\nMultiple parameters use block style (indented under '->'):\n  add() as pure\n    ->\n      a as Integer\n      b as Integer\n    <- result as Integer: a + b\n\nThere is NO return statement in EK9. Instead, you declare a named return variable with '<-'. The compiler ensures all code paths initialise that variable. If you provide a default value (': a + b'), the function body is optional. See Q50 for the full power of named returns.\n\nWhen computation requires multiple steps, add a body after the return declaration:\n  clamp() as pure\n    ->\n      number as Integer\n      min as Integer\n      max as Integer\n    <- result as Integer: number\n    if number < min\n      result: min\n    else if number > max\n      result: max\n\nBecause functions are types, they can be stored in variables, passed as parameters, returned from other functions, and collected in Lists. This enables patterns that require interfaces or abstract classes in other languages. See Q51 for abstract functions, Q52 for dynamic functions, Q54 for pure functions and the Consumer/Acceptor distinction, Q55 for delegates, and Q56 for higher-order functions.\n\nEK9 also enforces quality limits on ALL functions at compile time: cyclomatic complexity must be less than 11, nesting depth less than 4, statement count less than 20, and variable names must be descriptive. These limits cannot be disabled and apply equally to all function types.\n\nSee Q93 for defining classes. See Q106 for traits. See Q256 for the Void type when functions return nothing. See Q294 for function naming conventions.\n\nUse 'ek9 -h function' to see the full syntax reference. See Q319 for unused parameter detection. See Q588 for function extension and type hierarchies. See Q596 for function vs method distinction. See Q597 for function parameter patterns in depth.","ek9Example":"defines module qa.function\n\n  defines function\n\n    greet() as pure\n      -> name as String\n      <- message as String: \"Hello, \" + name\n\n    add() as pure\n      ->\n        a as Integer\n        b as Integer\n      <- result as Integer: a + b\n\n    clamp() as pure\n      ->\n        number as Integer\n        min as Integer\n        max as Integer\n      <- result as Integer: number\n      if number < min\n        result: min\n      else if number > max\n        result: max\n\n    factorial() as pure\n      -> n as Integer\n      <- result as Integer: 1\n      for i in 1 ... n\n        result: result * i\n\n  defines program\n\n    FunctionDemo()\n      stdout <- Stdout()\n\n      // Single-parameter function call\n      stdout.println(greet(\"World\"))\n\n      // Multi-parameter function call\n      stdout.println(`add(3, 4): ${add(3, 4)}`)\n\n      // Function with body logic\n      stdout.println(`clamp(15, 0, 10): ${clamp(15, 0, 10)}`)\n      stdout.println(`clamp(-5, 0, 10): ${clamp(-5, 0, 10)}`)\n      stdout.println(`clamp(7, 0, 10): ${clamp(7, 0, 10)}`)\n\n      // Pure function\n      stdout.println(`factorial(5): ${factorial(5)}`)","migrationContext":"Java: methods live inside classes, no standalone functions, static methods are a workaround, functional interfaces require SAM conversion, no compile-time quality enforcement. Python: def creates functions but they have no type contract, no compile-time parameter type checking, no quality enforcement. JavaScript: function declarations and arrow functions have no type safety, no quality enforcement, 'this' binding is confusing. Rust: fn defines functions with strong typing but functions are not types in the OOP sense, no compile-time quality limits. Go: func defines functions, first-class values but no type hierarchies, no purity enforcement, no quality limits. C#: methods inside classes, delegates are separate concept, no quality enforcement. Kotlin: fun defines functions, similar to Java methods, no standalone function types, no quality enforcement. Swift: func defines functions, closures are structural not nominal, no quality enforcement. EK9: functions ARE types with identity and inheritance hierarchies, compile-time quality enforcement on all functions, no return statement needed, named return variables ensure all paths initialise.","keywords":["beginner","block","complexity","define","first","function","immutable","intro","migrate","named","parameter","pure","quality","return","side-effect","start","type"],"primaryTopics":["function","define function","create function"],"typicalErrors":[{"error":"E06270","correct":"${add(3, 4)}","incorrect":"${add(3, 4, 5)}","explanation":"The function 'add' takes exactly two Integer parameters. Passing three arguments triggers E06270 — parameter mismatch. EK9 does not support varargs or default parameters. See ek9 -h E06270 for details."},{"error":"E50001","correct":"stdout.println(greet(\"World\"))","incorrect":"stdout.println(gret(\"World\"))","explanation":"A typo in the function name 'gret' means the compiler cannot find any identifier with that name. EK9 is case-sensitive and requires exact spelling. See ek9 -h E50001 for details."},{"error":"E01030","correct":"add() as pure\n      ->\n        a as Integer\n        b as Integer\n      <- result as Integer: a + b","incorrect":"add() as pure\n      ->\n        a as Integer\n        b as Integer\n      <- result as Integer: a + b\n\n    add() as pure\n      ->\n        a as Integer\n        b as Integer\n        c as Integer\n      <- result as Integer: a + b + c","explanation":"EK9 does not support function overloading. Defining two functions with the same name but different parameters triggers E01030. Use distinct names like 'add2' and 'add3', or use a single function with a List parameter. See ek9 -h E01030 for details."},{"error":"E01072","correct":"<- result as Integer: number","incorrect":"<- result as Integer: number\n      return result","explanation":"EK9 does not have a 'return' statement. Declare the return variable with '<-' and the compiler ensures all code paths initialise it. See ek9 -h E01072 for details."}],"companions":[],"oracleToolHint":{"tool":"ek9_scaffold","intent":"function","description":"Oracle can generate a function with parameters and return value declaration."}}
{"id":50,"category":"Getting Started","question":"How do function returns work without a return statement in EK9?","url":"https://ek9.io/qa/QA0050.html","alternatePhrasings":["Why does EK9 not have a return statement?","What does the question mark mean on a function return declaration?","How does the guarded assignment operator work with function returns?"],"answer":"EK9 deliberately has NO return statement. Instead, you declare a named return variable with '<-' and the compiler ensures all code paths initialise it. This eliminates an entire class of bugs where code paths miss a return.\n\nINITIALISED RETURNS\nThe simplest pattern provides a default value at declaration:\n  add() as pure\n    -> a as Integer, b as Integer\n    <- result as Integer: a + b\nHere 'result' is initialised to 'a + b' and the function needs no body.\n\nRETURNS WITH BODY\nWhen you need conditional logic, declare a default then modify:\n  clamp() as pure\n    -> number as Integer, min as Integer, max as Integer\n    <- result as Integer: number\n    if number < min\n      result: min\n    else if number > max\n      result: max\n\nUNINITIALISED RETURNS — THE '?' SUFFIX\nThe '?' on '<- result as Float?' does NOT mean Optional. It means the return variable is declared but NOT initialised at declaration. It WILL be assigned during the function body. This is essential for pure functions where you want to use ':=?' (guarded assignment) across different conditional branches:\n  ratingPercentage() as pure\n    -> percentage as Float\n    <- rtn as String: given percentage\n      <- rating as String: String()\n      when <= 0.5\n        rating:=? \"Low\"\n      when <= 0.8\n        rating:=? \"Medium\"\n      default\n        rating:=? \"High\"\n\nThe ':=?' operator assigns ONLY if the target is unset. Each branch sets the value exactly once. If a branch accidentally executed twice, ':=?' would fail rather than silently overwrite. This guarantees single-assignment semantics in pure functions.\n\nUNSET RETURN AS SIGNAL\nAnother powerful pattern uses an unset return to signal 'not found':\n  findIndex()\n    -> items as List of Integer, target as Integer\n    <- index as Integer: Integer()\n    low <- 0\n    high <- length items - 1\n    while low <= high and ~index?\n      mid <- (low + high) / 2\n      checkValue <- items.getOrDefault(mid, 0)\n      if checkValue == target\n        index: mid\n      else if checkValue < target\n        low: mid + 1\n      else\n        high: mid - 1\nThe return 'index' starts as unset (Integer() creates an unset Integer). The caller checks 'if result?' to see if a value was found. The '~index?' in the while condition means 'index is not set' — the loop exits when found.\n\nNo return statement means no early exits, no forgotten returns, and no unreachable code. The compiler verifies every path. See Q29 for unset variable semantics, Q51 for abstract function implementations that use ':=?' patterns, and Q54 for how purity connects to ':=?'. See Q93 for class methods which use the same return patterns. See Q256 for the Void type and implicit returns. See Q274 for why AI generates return statements. See Q289 for converting multiple returns to EK9. See Q316 for how EK9 detects discarded return values.","ek9Example":"defines module qa.functionreturns\n\n  defines function\n\n    add() as pure\n      ->\n        a as Integer\n        b as Integer\n      <- result as Integer: a + b\n\n    clamp() as pure\n      ->\n        number as Integer\n        min as Integer\n        max as Integer\n      <- result as Integer: number\n      if number < min\n        result: min\n      else if number > max\n        result: max\n\n    rate() as pure\n      -> score as Integer\n      <- rating as String?\n      premiumThreshold <- 80\n      standardThreshold <- 60\n      if score >= premiumThreshold\n        rating:=? \"Excellent\"\n      else if score >= standardThreshold\n        rating:=? \"Good\"\n      else\n        rating:=? \"Needs work\"\n\n    findIndex()\n      ->\n        items as List of Integer\n        target as Integer\n      <- index as Integer: Integer()\n      low <- 0\n      high <- length items - 1\n      while low <= high and ~index?\n        mid <- (low + high) / 2\n        checkValue <- items.getOrDefault(mid, 0)\n        if checkValue == target\n          index: mid\n        else if checkValue < target\n          low: mid + 1\n        else\n          high: mid - 1\n\n  defines program\n\n    ReturnDemo()\n      stdout <- Stdout()\n\n      // Initialised return — no body needed\n      stdout.println(`add(3, 4): ${add(3, 4)}`)\n\n      // Return with body — default modified conditionally\n      stdout.println(`clamp(15, 0, 10): ${clamp(15, 0, 10)}`)\n\n      // Uninitialised return with :=? guarded assignment\n      stdout.println(`rate(85): ${rate(85)}`)\n      stdout.println(`rate(70): ${rate(70)}`)\n      stdout.println(`rate(40): ${rate(40)}`)\n\n      // Unset return as signal — findIndex\n      numbers <- [1, 3, 5, 7, 9, 11]\n      found <- findIndex(numbers, 7)\n      if found?\n        stdout.println(`Found 7 at index: ${found}`)\n\n      notFound <- findIndex(numbers, 4)\n      if ~notFound?\n        stdout.println(\"4 not found in list\")","migrationContext":"Java: return statement required, easy to forget on some paths, compiler warns but does not always catch all cases, no guarded assignment concept. Python: return statement, implicit None return if omitted, no compile-time path analysis. JavaScript: return statement, undefined if omitted, no compile-time checking. Rust: last expression is implicit return OR explicit return keyword, no named return variable, no guarded assignment. Go: named returns exist but return statement still required, bare return uses named values but is considered bad practice. C#: return statement required, no guarded assignment. Kotlin: return statement or last expression, no named return variable, no guarded assignment. Swift: return statement or implicit single-expression return, no named return, no guarded assignment. EK9: NO return statement at all, named return variable with '<-', compiler verifies all paths initialise, '?' suffix for uninitialised declaration, ':=?' guarded assignment ensures single assignment in pure functions, unset return as signal pattern eliminates sentinel values.","keywords":["assignment","beginner","first","function","guarded","immutable","intro","isset","mark","named","null-safe","path","pure","question","return","safe","side-effect","signal","start","statement","uninitialised","variable"],"primaryTopics":["function return","return value","named return"],"typicalErrors":[{"error":"E08050","correct":"<- rating as String?\n      premiumThreshold <- 80\n      standardThreshold <- 60\n      if score >= premiumThreshold\n        rating:=? \"Excellent\"\n      else if score >= standardThreshold\n        rating:=? \"Good\"\n      else\n        rating:=? \"Needs work\"","incorrect":"<- rating as String?\n      if score >= 80\n        rating:=? \"Excellent\"\n      else if score >= 60\n        rating:=? \"Good\"","explanation":"If the return variable is uninitialised (? suffix) the compiler requires all paths to assign it. Missing the else branch means 'rating' could remain uninitialised. See ek9 -h E11064 for details."},{"error":"E50030","correct":"<- result as Integer: a + b","incorrect":"<- result as Integer: \"sum\"","explanation":"A String literal cannot initialise an Integer return variable. EK9 is strongly typed and requires compatible types in assignments. See ek9 -h E50030 for details."}],"companions":[]}
{"id":51,"category":"Getting Started","question":"What are abstract functions in EK9?","url":"https://ek9.io/qa/QA0051.html","alternatePhrasings":["How do abstract functions work as standalone types in EK9?","How do I implement an abstract function in EK9?","How do I use function polymorphism in EK9?"],"answer":"Abstract functions in EK9 are STANDALONE TYPES that define a callable contract. This is unique to EK9 — in every other mainstream language, abstract behaviour requires classes or interfaces. In EK9, a function can be abstract on its own:\n  mathOperation() as pure abstract\n    -> x as Float, y as Float\n    <- result as Float?\n\nThis declares a TYPE called 'mathOperation'. Any function that says 'is mathOperation' or 'extends mathOperation' becomes a concrete implementation of that type.\n\nIMPLEMENTING WITH 'is' OR 'extends'\nNamed functions implement an abstract function using 'is' or 'extends' (both are equivalent for functions):\n  addOp() is mathOperation as pure\n    -> x as Float, y as Float\n    <- result as Float: x + y\n\n  subtractOp() is mathOperation as pure\n    -> x as Float, y as Float\n    <- result as Float: x - y\n\nFUNCTION POLYMORPHISM\nBecause abstract functions are types, you get true polymorphism. Store implementations in a list and iterate:\n  for op in [addOp, subtractOp, multiplyOp]\n    stdout.println(`Result: ${op(10.0, 3.0)}`)\n\nThis is polymorphic dispatch — each function in the list is called through the same abstract type. No interfaces, no classes, no boilerplate.\n\nFUNCTION DELEGATES\nVariables can hold function references via their abstract type:\n  currentOp as mathOperation: addOp\n  result <- currentOp(5.0, 3.0)\n  currentOp: subtractOp\n  result2 <- currentOp(5.0, 3.0)\n\nWithout parentheses, 'addOp' is a REFERENCE (delegate). With parentheses, 'addOp(5.0, 3.0)' is a CALL. This distinction is fundamental.\n\nWhy is this more powerful than other languages? In Java, you need a functional interface (a class-like construct) to achieve the same. In Python, functions are first-class but have no type contract. In Rust, closures use Fn traits (structural) but you cannot name closure types directly. In Go, function types exist but cannot form inheritance hierarchies. EK9 abstract functions give you type safety, polymorphism, and named types — all without classes.\n\nSee Q52 for dynamic functions that implement abstract functions inline. See Q55 for passing functions as delegates. See Q56 for higher-order functions that return delegates. See Q59 for using function delegates in stream pipelines. See Q89 for how abstract functions serve as stream pipeline stage types. See Q103 for abstract classes and methods. See Q235 for complete stream operations reference. See Q588 for function extension and type hierarchies. See Q589 for abstract function implementation patterns.","ek9Example":"defines module qa.abstractfunction\n\n  defines function\n\n    mathOperation() as pure abstract\n      ->\n        x as Float\n        y as Float\n      <- result as Float?\n\n    addOp() is mathOperation as pure\n      ->\n        x as Float\n        y as Float\n      <- result as Float: x + y\n\n    subtractOp() is mathOperation as pure\n      ->\n        x as Float\n        y as Float\n      <- result as Float: x - y\n\n    multiplyOp() is mathOperation as pure\n      ->\n        x as Float\n        y as Float\n      <- result as Float: x * y\n\n  defines program\n\n    AbstractFunctionDemo()\n      stdout <- Stdout()\n\n      // Polymorphic dispatch — iterate over implementations\n      ops <- [addOp, subtractOp, multiplyOp]\n      for op in ops\n        stdout.println(`op(10.0, 3.0) = ${op(10.0, 3.0)}`)\n\n      // Function delegate — variable holds a reference\n      currentOp as mathOperation: addOp\n      stdout.println(`delegate: ${currentOp(5.0, 3.0)}`)\n\n      // Reassign to different implementation\n      currentOp: multiplyOp\n      stdout.println(`reassigned: ${currentOp(5.0, 3.0)}`)","migrationContext":"Java: requires functional interfaces (SAM types) like Predicate<T> or Function<T,R> which are class-like constructs, not standalone function types, @FunctionalInterface annotation is documentation only. Python: no abstract function concept, abc.abstractmethod requires a class wrapper, functions have no type contract. JavaScript: no abstract functions, no type system for functions, TypeScript has callable interfaces but they are structural. Rust: Fn/FnMut/FnOnce traits are structural not nominal, cannot name closure types without impl Trait or dyn Trait, no standalone abstract function type. Go: type aliases for function signatures (type MathOp func(float64, float64) float64) but no inheritance hierarchy, no polymorphic dispatch. C#: delegates define callable types but require separate delegate declarations, not function inheritance. Kotlin: functional types are structural ((Int, Int) -> Int), fun interface for SAM but still requires class-like construct. Swift: closures are structural, protocol-based approach needed for nominal typing. EK9: abstract functions ARE standalone types with nominal identity, implementations use 'is' or 'extends' for true function type hierarchies, polymorphic dispatch through abstract function variables, no class wrapper needed.","keywords":["abstract","beginner","call","contract","delegate","extends","first","function","handler","implement","intro","is","migrate","polymorphism","reference","sealed","standalone","start","type","visitor"],"primaryTopics":[],"typicalErrors":[{"error":"E05030","correct":"addOp() is mathOperation as pure","incorrect":"addOp() is addOp as pure","explanation":"Functions are closed by default. Extending a non-abstract, non-open function triggers E05030 — not open to be extended. Only abstract or open functions can be extended. See ek9 -h E05030 for details."}],"companions":[]}
{"id":52,"category":"Getting Started","question":"What are dynamic functions and how do they differ from lambdas?","url":"https://ek9.io/qa/QA0052.html","alternatePhrasings":["How do EK9 dynamic functions differ from lambdas in other languages?","Why does EK9 use dynamic functions instead of lambdas?","What is the inline dynamic function syntax in EK9?"],"answer":"EK9 dynamic functions look superficially like lambdas but are fundamentally different in four critical ways. Understanding these differences is key to understanding EK9's power.\n\n1. NOMINALLY TYPED\nEvery dynamic function MUST implement a named abstract function type. You cannot create an anonymous untyped lambda. This ensures every closure has a documented, compiler-verified contract:\n  myAdd <- () is mathOperation as pure function\n    result:=? x + y\n\nIn Java, a lambda like '(x, y) -> x + y' is structurally typed — the compiler guesses what interface it matches. In EK9, you explicitly declare 'is mathOperation', making the intent clear.\n\n2. PARAMETER INFERENCE\nDynamic functions inherit parameter names from their abstract parent. You do NOT re-declare them. The abstract function 'mathOperation' declares parameters 'x' and 'y', so the dynamic function body can use 'x' and 'y' directly. This is DRY — the contract is defined once.\n\n3. BLOCK vs INLINE SYNTAX\nBlock form uses 'as function' keyword with an indented body:\n  myAdd <- () is mathOperation as pure function\n    result:=? x + y\n\nInline form wraps the body in parentheses — this is the closest EK9 gets to a lambda:\n  myAdd <- () is mathOperation as pure (result:=? x + y)\n\nInline is for single expressions. Multi-statement bodies require block form.\n\n4. QUALITY ENFORCEMENT\nDynamic functions are subject to the SAME compile-time quality rules as named functions: cyclomatic complexity less than 11, nesting less than 4, descriptive variable names. In Java and Python, lambdas have zero quality enforcement — you can write arbitrarily complex, unreadable lambdas.\n\nBOTH 'is' AND 'extends' WORK\nBoth keywords are equivalent for function implementation:\n  using_is <- () is mathOperation as pure function\n    result:=? x + y\n  using_extends <- () extends mathOperation as pure\n    result:=? x - y\n\nWHY NOT ANONYMOUS LAMBDAS?\nEK9 deliberately chose this design:\n- READABILITY: Every dynamic function declares what type it implements\n- TYPE SAFETY: Compiler verifies the signature match (not structural guessing)\n- QUALITY: Same enforcement as named functions\n- TRACEABLE: Data flow is explicit, not hidden\n\nThe result is that EK9 dynamic functions are more verbose than '(x, y) -> x + y' but dramatically safer, more readable, and maintainable. See Q53 for how variable capture works. See Q51 for abstract function types. See Q59 for using dynamic functions in stream pipelines. See Q89 for stream pipeline basics. See Q115 for dynamic classes. See Q235 for complete stream operations reference. See Q601 for how dynamic functions replace nested functions.","ek9Example":"defines module qa.dynamicfunction\n\n  defines function\n\n    mathOperation() as pure abstract\n      ->\n        x as Float\n        y as Float\n      <- result as Float?\n\n  defines program\n\n    DynamicFunctionDemo()\n      stdout <- Stdout()\n\n      // Block form with 'as function'\n      dynamicAdd <- () is mathOperation as pure function\n        result:=? x + y\n\n      stdout.println(`block add: ${dynamicAdd(7.0, 2.0)}`)\n\n      // Block form with 'extends'\n      dynamicSub <- () extends mathOperation as pure\n        result:=? x - y\n\n      stdout.println(`block sub: ${dynamicSub(7.0, 2.0)}`)\n\n      // Inline form — closest EK9 gets to a lambda\n      inlineMul <- () is mathOperation as pure (result:=? x * y)\n\n      stdout.println(`inline mul: ${inlineMul(7.0, 2.0)}`)\n\n      // Multiple inline functions in a list\n      ops <- [\n        () is mathOperation as pure (result:=? x + y),\n        () is mathOperation as pure (result:=? x - y),\n        () is mathOperation as pure (result:=? x * y)\n      ]\n\n      for op in ops\n        stdout.println(`op(6.0, 3.0) = ${op(6.0, 3.0)}`)","migrationContext":"Java: lambdas are structurally typed against functional interfaces, (x, y) -> x + y lets compiler guess the target type, no explicit type declaration at usage, lambda bodies can be arbitrarily complex with no quality enforcement, method references (::) are a separate syntax. Python: lambda limited to single expression, def functions have no type relationship, closures capture by reference with notorious late-binding bug. JavaScript: arrow functions ((x, y) => x + y) are structural, no type contract, no quality enforcement, 'this' binding confusion, mutable closure state is major bug source. Rust: closures are structurally typed via Fn/FnMut/FnOnce traits, cannot name closure types without impl Trait, no compile-time quality limits. Go: anonymous functions func(x, y float64) float64 are structural, no type hierarchies, no quality enforcement. Kotlin: lambdas { x, y -> x + y } are structural, fun interface for SAM but structural matching, no quality enforcement on lambdas. Swift: closures { (x: Double, y: Double) -> Double in x + y } are structural, no nominal typing for closures. EK9: dynamic functions are NOMINALLY typed (must implement named abstract function), parameters inherited from abstract parent (DRY), block and inline syntax, same compile-time quality enforcement as named functions.","keywords":["abstract","anonymous","beginner","block","capture","closure","dynamic","enforcement","first","function","implement","inference","inline","intro","lambda","migrate","nominal","parameter","quality","start","structural","swift","typed"],"primaryTopics":["dynamic function","lambda","anonymous function"],"typicalErrors":[{"error":"E08120","correct":"dynamicAdd <- () is mathOperation as pure function\n        result:=? x + y","incorrect":"dynamicAdd <- () is mathOperation as pure function\n        result += x","explanation":"If the abstract function is declared 'as pure', the dynamic function body cannot use mutation operators like +=. This triggers E08120 — mutating variables not allowed in pure scope. Use reassignment with :=? or : instead. See ek9 -h E08120 for details."}],"companions":[]}
{"id":53,"category":"Getting Started","question":"How do closures and variable capture work in EK9?","url":"https://ek9.io/qa/QA0053.html","alternatePhrasings":["How does EK9 capture variables in dynamic functions?","What is the difference between capture by value and capture by reference?","How do named capture parameters work in EK9?"],"answer":"EK9 dynamic functions can capture variables from their enclosing scope, making them closures. But EK9's capture mechanism is fundamentally different from every other mainstream language in three ways.\n\n1. EXPLICIT CAPTURE\nCaptured variables are listed explicitly in parentheses BEFORE the 'is' keyword:\n  scaleFactor <- 10.0\n  scaled <- (scaleFactor) is mathOperation as pure function\n    result:=? x * scaleFactor + y\n\nIn Java, JavaScript, and Python, capture is AUTOMATIC and HIDDEN — any variable from the enclosing scope can be silently captured. This leads to accidental captures, unexpected dependencies, and hard-to-trace bugs. EK9 makes data flow visible.\n\n2. CAPTURE BY VALUE\nCaptured variables are COPIED at creation time. The dynamic function gets its own independent copy. Changing the original variable after creation does NOT affect the captured value:\n  factor <- 10.0\n  scaled <- (factor) is mathOperation as pure function\n    result:=? x * factor\n  factor: 999.0\n  scaled(3.0, 1.0)    //Still uses 10.0, not 999.0\n\nThis eliminates an entire class of bugs. In Java, closures capture by reference (must be effectively final as a workaround). In JavaScript and Python, closures capture by reference and CAN mutate — leading to the notorious Python late-binding bug and JavaScript loop-capture bugs.\n\n3. NAMED CAPTURE PARAMETERS\nCaptures can have explicit values, making the function self-documenting:\n  configured <- (factor: 2.0, offset: 5.0) is mathOperation as pure function\n    result:=? x * factor + offset\n\nThe names and values are visible at the creation site. No need to look at surrounding code to understand what values the function uses.\n\nMULTIPLE CAPTURES\nAny number of variables can be captured:\n  (min, max, label) is validator as function\n    ...\n\nCAPTURE CONSTRAINTS\n- Variables must be named — literals cannot be captured directly\n- Unused captures are a compiler error (E11018)\n- Captured variables count toward coupling metrics\n- The compiler tracks captures for quality enforcement\n\nCAPTURE IN INLINE SYNTAX\nInline dynamic functions also support captures:\n  biased <- (bias) is mathOperation as pure (result:=? x + y + bias)\n\nSee Q52 for dynamic function syntax. See Q57 for using captures to change class behaviour without subclassing. See Q58 for how captures enable stream pipeline integration. See Q59 for using captures in stream pipeline stages. See Q89 for stream pipeline basics. See Q115 for dynamic classes which use the same capture syntax. See Q235 for complete stream operations reference. See Q319 for unused closure capture detection (E11018). See Q601 for how dynamic functions with captures replace nested functions.","ek9Example":"defines module qa.closurecapture\n\n  defines function\n\n    mathOperation() as pure abstract\n      ->\n        x as Float\n        y as Float\n      <- result as Float?\n\n  defines program\n\n    CaptureDemo()\n      stdout <- Stdout()\n\n      // Explicit capture by value\n      factor <- 10.0\n      scaled <- (factor) is mathOperation as pure function\n        result:=? x * factor + y\n\n      stdout.println(`captured: ${scaled(3.0, 1.0)}`)\n\n      // Changing original does NOT affect the captured copy\n      factor: 999.0\n      stdout.println(`factor is now: ${factor}`)\n      stdout.println(`after change: ${scaled(3.0, 1.0)}`)\n\n      // Named capture parameters — self-documenting\n      configured <- (multiplier: 2.0, offset: 5.0) is mathOperation as pure function\n        result:=? x * multiplier + offset\n\n      stdout.println(`named: ${configured(4.0, 0.0)}`)\n\n      // Multiple captures\n      base <- 100.0\n      adjustment <- 0.1\n      adjusted <- (base, adjustment) is mathOperation as pure function\n        result:=? base + (x * adjustment) + y\n\n      stdout.println(`multiple: ${adjusted(50.0, 3.0)}`)\n\n      // Inline capture\n      bias <- 10.0\n      biased <- (bias) is mathOperation as pure (result:=? x + y + bias)\n      stdout.println(`inline: ${biased(1.0, 2.0)}`)","migrationContext":"Java: closures capture by reference, must be effectively final (workaround for mutable state bugs), no explicit capture list, accidental capture of large objects causes memory leaks, lambda capture is hidden and automatic. Python: closures capture by reference with notorious late-binding bug (loop variable captured by reference not value), nonlocal keyword for mutation, no explicit capture list, no compile-time capture validation. JavaScript: closures capture by reference, mutable shared state is a major bug source, classic 'var in for loop' capture bug, no explicit capture list, no quality enforcement. Rust: closures can capture by reference (borrow) or by value (move), move keyword forces value capture, compiler enforces borrow rules but capture is still implicit, Fn/FnMut/FnOnce traits determine what captures can do. Go: closures capture by reference, mutable shared state, goroutine + closure capture bugs are common, no explicit capture list. Kotlin: closures capture by reference, CAN mutate captured vars (unlike Java), no explicit capture list. Swift: closures capture by reference by default, capture list [weak self, unowned x] for value capture, @escaping annotation required. EK9: capture is EXPLICIT (listed in parentheses), by VALUE (independent copy), named capture parameters for self-documenting code, unused captures are compiler errors, captures count toward quality metrics.","keywords":["beginner","bug","capture","closure","copy","explicit","first","function","independent","intro","migrate","mutable","named","parameter","reference","start","state","swift","value","variable"],"primaryTopics":["closure","variable capture","captured variable"],"typicalErrors":[{"error":"E08010","correct":"scaled <- (factor) is mathOperation as pure function\n        result:=? x * factor + y","incorrect":"scaled <- (factor, base) is mathOperation as pure function\n        result:=? x * factor + y","explanation":"Capturing a variable that has not been defined yet in the program flow triggers E08010 — variable not defined. The variable 'base' is declared later in the program, so it cannot be captured at this point. See ek9 -h E08010 for details."},{"error":"E08120","correct":"result:=? x * factor + y","incorrect":"factor *= 2","explanation":"In a pure dynamic function, mutating captured variables is forbidden. This triggers E08120 — mutating variables not allowed in pure scope. Captured values are immutable copies in pure functions. See ek9 -h E08120 for details."}],"companions":[]}
{"id":54,"category":"Getting Started","question":"What are pure functions and what is the difference between Consumer and Acceptor?","url":"https://ek9.io/qa/QA0054.html","alternatePhrasings":["How does EK9 enforce function purity?","Why does EK9 have both Consumer and Acceptor?","What built-in abstract function types does EK9 provide?"],"answer":"Pure functions in EK9 are declared with 'as pure' and the compiler ENFORCES purity — no side effects, no mutable external state, no calling impure functions. This is not a hint or annotation — it is a compile-time guarantee.\n\nDECLARING PURE FUNCTIONS\n  factorial() as pure\n    -> n as Integer\n    <- result as Integer: 1\n    for i in 1 ... n\n      result: result * i\n\nWHY NOT 'result *= i'?\nMutation operators like *=, +=, :=: are NOT pure — they mutate the LHS in place and COULD also affect the RHS as a side effect. Reassignment ('result: result * i') uses the pure '*' operator to create a new value, then reassigns. Only the LHS changes, the RHS is guaranteed untouched.\n\nPURITY IS INHERITED\nWhen an abstract function is declared pure, ALL implementations MUST also be pure:\n  mathOperation() as pure abstract\n    -> x as Float, y as Float\n    <- result as Float?\n\n  add() is mathOperation as pure    //Must be pure — compiler enforces\n    -> x as Float, y as Float\n    <- result as Float: x + y\n\nCONSUMER vs ACCEPTOR — THE KEY DISTINCTION\nEK9 provides built-in abstract function types. The most important pair is Consumer and Acceptor — they have the SAME signature but different purity:\n\n  Consumer of type T as pure abstract    //PURE — cannot mutate\n    -> t as T\n\n  Acceptor of type T as abstract         //NOT pure — can mutate\n    -> t as T\n\nBoth take a single parameter and return nothing. Consumer is pure — it can only READ the value. Acceptor is NOT pure — it can modify state. This distinction is why Optional and Result have both whenOk(Consumer) for read-only access and whenOk(Acceptor) for mutation.\n\nOTHER BUILT-IN ABSTRACT FUNCTION TYPES\nEK9 provides a complete set, all generic:\n  Supplier of T as pure abstract         //No input, returns T\n    <- r as T?\n  Predicate of T as pure abstract        //Takes T, returns Boolean\n    -> t as T\n    <- r as Boolean?\n  UnaryOperator of T as pure abstract    //Takes T, returns T\n    -> t as T\n    <- r as T?\n  Comparator of T as pure abstract       //Compares two T values\n    -> t1 as T, t2 as T\n    <- r as Integer?\n  Assessor of T as abstract              //Like Predicate but NOT pure\n    -> t as T\n    <- r as Boolean?\n\nAnd Bi-parameter variants: BiConsumer of (T, U), BiAcceptor of (T, U), BiPredicate of (T, U).\n\nNOTICE THE PATTERN: Pure variants (Consumer, Predicate, UnaryOperator, Comparator) guarantee safety. Non-pure variants (Acceptor, Assessor) allow mutation when needed. This gives you precise control over what operations can and cannot do.\n\nNo other mainstream language provides this. Java has Consumer but it can mutate freely. Rust has Fn/FnMut but these are structural traits. EK9 gives you NOMINAL purity enforcement with a complete type hierarchy.\n\nSee Q49 for defining functions. See Q50 for how ':=?' guarded assignment is essential in pure returns. See Q53 for how purity affects closure capture. See Q55 for passing these as delegates. See Q48 for how Result uses Consumer and Acceptor. See Q89 for how Predicate, Comparator, and UnaryOperator are used as stream pipeline stages. See Q106 for traits which define behavioral contracts. See Q215 for sanitized parameters. See Q235 for complete stream operations reference mapping each operation to its function type. See Q262 for observer pattern using function delegates as event listeners. See Q273 for purity as security boundary.","ek9Example":"defines module qa.pureconsumeracceptor\n\n  defines function\n\n    mathOperation() as pure abstract\n      ->\n        x as Float\n        y as Float\n      <- result as Float?\n\n    addOp() is mathOperation as pure\n      ->\n        x as Float\n        y as Float\n      <- result as Float: x + y\n\n    factorial() as pure\n      -> n as Integer\n      <- result as Integer: 1\n      for i in 1 ... n\n        result: result * i\n\n    printValue()\n      -> item as String\n      stdout <- Stdout()\n      stdout.println(`Processing: ${item}`)\n\n  defines program\n\n    PureDemo()\n      stdout <- Stdout()\n\n      // Pure function — compiler enforces no side effects\n      stdout.println(`factorial(6): ${factorial(6)}`)\n\n      // Pure abstract function — implementations must be pure\n      op as mathOperation: addOp\n      stdout.println(`pure delegate: ${op(5.0, 3.0)}`)\n\n      // Consumer is pure — read-only access\n      r1 <- Result(\"Steve\", Integer())\n      if r1?\n        stdout.println(`ok: ${r1.ok()}`)\n\n      // Built-in types use Consumer/Acceptor distinction\n      opt <- Optional(\"Hello\")\n      if opt?\n        stdout.println(`optional: ${opt.get()}`)","migrationContext":"Java: Consumer<T> can mutate freely (no purity), Function<T,R> can have side effects, no distinction between pure and impure functional interfaces, @FunctionalInterface is just documentation. Python: no built-in abstract function types, no purity concept, any function can have side effects. JavaScript: no type system for functions, no purity enforcement, everything can mutate. Rust: Fn (immutable borrow), FnMut (mutable borrow), FnOnce (takes ownership) — similar concept but structural not nominal, no named types for closures. Go: no abstract function concept, no purity enforcement, all functions can have side effects. C#: Action<T> and Func<T,R> delegates but no purity enforcement, no Consumer/Acceptor distinction. Kotlin: (T) -> Unit for both pure and impure, no compile-time purity, no distinction. Swift: (T) -> Void for both, @Sendable for some safety but no purity enforcement. EK9: Consumer is PURE (read-only), Acceptor is NOT pure (can mutate), compiler enforces this distinction, complete set of built-in abstract function types with pure/non-pure variants, purity is inherited through implementations.","keywords":["abstract","acceptor","assessor","beginner","comparator","consumer","effect","enforce","first","function","immutable","intro","migrate","operator","predicate","pure","purity","side","side-effect","start","supplier","unary"],"primaryTopics":[],"typicalErrors":[{"error":"E05150","correct":"addOp() is mathOperation as pure","incorrect":"addOp() is mathOperation","explanation":"If the abstract parent is declared 'as pure', the implementation must also be 'as pure'. Omitting 'as pure' when the parent requires it triggers E05150 — purity mismatch. See ek9 -h E05150 for details."},{"error":"E08120","correct":"result: result * i","incorrect":"result *= i","explanation":"In a pure function, mutation operators like *= are forbidden because they modify the LHS in-place. This triggers E08120 — mutating variables not allowed in pure scope. Use reassignment (result: result * i) to create a new value instead. See ek9 -h E08120 for details."}],"companions":[]}
{"id":55,"category":"Getting Started","question":"How do I pass functions as delegates in EK9?","url":"https://ek9.io/qa/QA0055.html","alternatePhrasings":["How do function delegates work in EK9?","How do I store a function in a variable in EK9?","How do I pass a function as a parameter in EK9?"],"answer":"Because EK9 functions are types, you can store them in variables, pass them as parameters, and return them from other functions. A variable that holds a function reference is called a delegate.\n\nSTORING A FUNCTION IN A VARIABLE\nDeclare a variable typed as the abstract function, assign a concrete implementation:\n  processor as Processor: UpperProcessor\n  result <- processor(\"Hello\")    //Calls UpperProcessor\n\nWithout parentheses, 'UpperProcessor' is a REFERENCE. With parentheses, 'UpperProcessor(\"Hello\")' is a CALL. This distinction is critical.\n\nREASSIGNING DELEGATES\nChange the function a delegate points to at runtime:\n  processor: LowerProcessor\n  result <- processor(\"Hello\")    //Now calls LowerProcessor\n\nPASSING FUNCTIONS AS PARAMETERS\nFunctions can accept other functions as parameters. Use the abstract function type:\n  applyToGreeting()\n    -> transform as Processor\n    <- result as String: transform(\"Hello World\")\n\nCall with any implementation:\n  upper <- applyToGreeting(UpperProcessor)\n  lower <- applyToGreeting(LowerProcessor)\n\nUSING BUILT-IN GENERIC FUNCTION TYPES\nFor common patterns, use the built-in abstract function types instead of defining your own:\n  Consumer of T       //Pure: takes T, returns nothing\n  Acceptor of T       //Not pure: takes T, returns nothing\n  Predicate of T      //Pure: takes T, returns Boolean\n  UnaryOperator of T  //Pure: takes T, returns T\n  Supplier of T       //Pure: takes nothing, returns T\n  Comparator of T     //Pure: compares two T values\n\nThese save you from defining abstract functions for standard patterns. See Q54 for the pure/non-pure distinction.\n\nFUNCTIONS IN COLLECTIONS\nStore delegates in a list using the idiomatic literal syntax:\n  processors <- [UpperProcessor, LowerProcessor]\n  for proc in processors\n    stdout.println(proc(\"Hello\"))\n\nThis creates a List of Processor inferred from the contents. Much cleaner than explicit List construction.\n\nSee Q51 for abstract functions. See Q56 for higher-order functions that return delegates. See Q57 for using delegates to change class behaviour. See Q59 for dynamic functions as pipeline stage delegates. See Q89 for using function delegates in stream pipelines. See Q109 for composition using delegation. See Q235 for complete stream operations reference. See Q262 for observer pattern using function delegates as event listeners.","ek9Example":"defines module qa.functiondelegate\n\n  defines function\n\n    Processor() as abstract\n      -> text as String\n      <- result as String?\n\n    UpperProcessor() extends Processor\n      -> text as String\n      <- result as String: text.upperCase()\n\n    LowerProcessor() extends Processor\n      -> text as String\n      <- result as String: text.lowerCase()\n\n    applyToGreeting()\n      -> transform as Processor\n      <- result as String: transform(\"Hello World\")\n\n  defines program\n\n    DelegateDemo()\n      stdout <- Stdout()\n\n      // Direct call — with parentheses\n      stdout.println(`direct: ${UpperProcessor(\"Hello\")}`)\n\n      // Delegate — variable holds function reference\n      processor as Processor: UpperProcessor\n      stdout.println(`delegate: ${processor(\"Hello\")}`)\n\n      // Reassign delegate\n      processor: LowerProcessor\n      stdout.println(`reassigned: ${processor(\"Hello\")}`)\n\n      // Pass function as parameter\n      upper <- applyToGreeting(UpperProcessor)\n      lower <- applyToGreeting(LowerProcessor)\n      stdout.println(`upper: ${upper}`)\n      stdout.println(`lower: ${lower}`)\n\n      // Functions in a list — idiomatic literal syntax\n      processors <- [UpperProcessor, LowerProcessor]\n      for proc in processors\n        stdout.println(`list: ${proc(\"World\")}`)","migrationContext":"Java: functional interfaces as parameter types, method references with :: syntax, no first-class function variables (only interface references), lambda-to-interface conversion is implicit structural matching. Python: functions are first-class values, pass by name, no type contract on parameters, duck typing. JavaScript: functions are first-class, pass by reference, no type safety, callback hell pattern. Rust: fn pointers for simple functions, Fn/FnMut/FnOnce trait objects for closures, dyn Fn for dynamic dispatch, Box<dyn Fn> for ownership, complex lifetime annotations. Go: function values, pass by name, no type hierarchies, limited type safety. C#: delegates and Func/Action types, explicit delegate declaration needed, += multicast delegates add complexity. Kotlin: function types (String) -> String are structural, no nominal type identity, SAM conversion for interfaces. Swift: function types (String) -> String are structural, @escaping for stored closures. EK9: functions ARE types, store in variables typed as abstract function, pass as parameters using abstract function type, collect in lists with literal syntax, delegate vs call distinguished by parentheses, built-in generic function types for common patterns.","keywords":["beginner","call","collection","consumer","delegate","first","function","intro","list","parameter","parentheses","pass","predicate","reference","start","store","variable"],"primaryTopics":["function delegate","pass function","callback"],"typicalErrors":[{"error":"E50030","correct":"UpperProcessor() extends Processor","incorrect":"UpperProcessor() as pure","explanation":"Without 'extends Processor', UpperProcessor is a standalone function with no type relationship to Processor. Assigning it to a Processor variable fails because the types are incompatible. Use 'extends' or 'is' to establish the function hierarchy. See ek9 -h E50030 for details."}],"companions":[]}
{"id":56,"category":"Getting Started","question":"What are higher-order functions and why should I use them?","url":"https://ek9.io/qa/QA0056.html","alternatePhrasings":["How do I create a function that returns a function in EK9?","How do I use the strategy pattern with EK9 functions?","Why are higher-order functions useful in EK9?"],"answer":"A higher-order function is a function that either accepts a function as a parameter, returns a function as its result, or both. EK9's type system makes higher-order functions natural and type-safe because functions ARE types.\n\nFUNCTION RETURNING A FUNCTION\nA function can return a delegate based on runtime conditions:\n  getProcessor()\n    -> mode as String\n    <- processor as Processor?\n    if mode == \"upper\"\n      processor := UpperProcessor\n    else\n      processor := LowerProcessor\n\nThe caller gets a function reference and calls it:\n  transform <- getProcessor(\"upper\")\n  result <- transform(\"hello\")\n\nCONDITIONAL FUNCTION SELECTION (TERNARY)\nEK9's ternary syntax is especially elegant for selecting between two functions:\n  selectOperation()\n    -> value as Float\n    <- op as mathOperation: value < 0.0 <- addOp else subtractOp\n\nThis returns addOp when value is negative, subtractOp otherwise. One line, completely readable.\n\nWHY USE HIGHER-ORDER FUNCTIONS?\n\n1. STRATEGY PATTERN — Replace if/else chains with function selection:\n  Instead of: if mode == \"A\" then doA() else if mode == \"B\" then doB()\n  Use: processor <- getProcessor(mode) then processor(data)\n\n2. CONFIGURABLE ALGORITHMS — Pass behaviour as a parameter:\n  processList()\n    -> items as List of String, transform as Processor\n    <- results as List of String: List() of String\n    for item in items\n      results += transform(item)\n\n3. COMPOSITION — Build complex behaviour from simple functions:\n  Pipeline stages, each a function delegate, composed at runtime.\n\n4. TESTABILITY — Mock behaviour by passing different function delegates.\n\nIn Java, the strategy pattern requires defining an interface, implementing it in separate classes, and wiring them together. In EK9, you just pass a function. In Python, you can pass functions but there is no type contract. In Rust, you need trait objects. EK9 gives you the safety of typed strategies with the simplicity of passing functions.\n\nSee Q51 for abstract function types. See Q54 for pure function constraints on strategy selection. See Q55 for function delegates. See Q57 for using higher-order functions to change class behaviour without subclassing. See Q58 for generic function templates. See Q59 for higher-order functions in stream pipelines. See Q89 for stream pipeline basics.","ek9Example":"defines module qa.higherorderfunction\n\n  defines function\n\n    Processor() as abstract\n      -> text as String\n      <- result as String?\n\n    UpperProcessor() extends Processor\n      -> text as String\n      <- result as String: text.upperCase()\n\n    LowerProcessor() extends Processor\n      -> text as String\n      <- result as String: text.lowerCase()\n\n    getProcessor()\n      -> mode as String\n      <- processor as Processor?\n      if mode == \"upper\"\n        processor := UpperProcessor\n      else\n        processor := LowerProcessor\n\n    processList()\n      ->\n        items as List of String\n        transform as Processor\n      <- results as List of String: List() of String\n      for item in items\n        results += transform(item)\n\n  defines program\n\n    HigherOrderDemo()\n      stdout <- Stdout()\n\n      // Function returning a function\n      upper <- getProcessor(\"upper\")\n      stdout.println(`upper: ${upper(\"hello\")}`)\n\n      lower <- getProcessor(\"lower\")\n      stdout.println(`lower: ${lower(\"HELLO\")}`)\n\n      // Function accepting a function — configurable algorithm\n      words <- [\"Hello\", \"World\", \"EK9\"]\n      uppered <- processList(words, UpperProcessor)\n      for word in uppered\n        stdout.println(`processed: ${word}`)\n\n      // Strategy selection — swap behaviour at runtime\n      modes <- [\"upper\", \"lower\", \"upper\"]\n      for mode in modes\n        proc <- getProcessor(mode)\n        stdout.println(`${mode}: ${proc(\"Test\")}`)","migrationContext":"Java: requires functional interfaces as strategy types, method references (::) or lambdas, verbose anonymous class pattern for pre-Java-8, no type hierarchy for strategies. Python: functions are first-class so higher-order works naturally, but no type contracts, no compile-time safety, duck typing means errors at runtime. JavaScript: functions are first-class, higher-order is common (callbacks, promises), but no type safety, callback hell, no quality enforcement. Rust: higher-order functions use Fn trait bounds or fn pointers, complex lifetime annotations for closures, cannot name return types without impl Trait, verbose Box<dyn Fn> for stored delegates. Go: higher-order functions work with function types, but no type hierarchies, no generics until 1.18, verbose type declarations. C#: Func<T,R> and Action<T> for higher-order patterns, delegate types, LINQ is built on higher-order functions. Kotlin: higher-order functions with structural types, inline keyword for performance, but no nominal function type hierarchies. Swift: higher-order functions with structural closure types, @escaping annotation complexity. EK9: higher-order functions are natural because functions ARE types, ternary syntax for elegant function selection, abstract function types provide compile-time contracts, no verbose interface boilerplate, complete type safety.","keywords":["beginner","compose","conditional","configure","delegate","first","function","higher","intro","migrate","order","pattern","return","select","start","strategy","ternary"],"primaryTopics":["higher order function","function as parameter"],"typicalErrors":[{"error":"E07110","correct":"Processor() as abstract","incorrect":"Processor() as pure","explanation":"A pure function must have a body to be pure. Removing 'abstract' and adding 'pure' without a concrete body triggers E07110. See ek9 -h E07110 for details."},{"error":"E05160","correct":"UpperProcessor() extends Processor","incorrect":"UpperProcessor() extends Processor as pure","explanation":"If the parent function is not pure, child functions must also not be pure. Adding 'as pure' on a child of a non-pure parent triggers E05160 — purity mismatch in type hierarchy. See ek9 -h E05160 for details."}],"companions":[]}
{"id":57,"category":"Getting Started","question":"How can I change method behaviour on a class without subclassing?","url":"https://ek9.io/qa/QA0057.html","alternatePhrasings":["How do I use the strategy pattern with function delegates in EK9?","How do I inject behaviour into a class using functions in EK9?","How do I customise class behaviour per instance in EK9?"],"answer":"In EK9, you can change a class's method behaviour without subclassing by using function delegate fields. Supply a function as a constructor parameter, store it as a field, and call it from the method. Different instances of the same class can have different behaviour.\n\nTHE PATTERN\nDefine an abstract function for the behaviour contract:\n  Processor() as abstract\n    -> text as String\n    <- result as String?\n\nDefine a class with a function delegate field:\n  TextHandler\n    processor as Processor?\n\n    TextHandler()\n      -> handler as Processor\n      this.processor :=: handler\n\n    process()\n      -> text as String\n      <- result as String: processor(text)\n\nNow create instances with different behaviour:\n  upper <- TextHandler(UpperProcessor)\n  lower <- TextHandler(LowerProcessor)\n  upper.process(\"hello\")    //Returns \"HELLO\"\n  lower.process(\"hello\")    //Returns \"hello\"\n\nSame class, same method signature, different behaviour per instance. No subclassing, no inheritance hierarchy, no override boilerplate.\n\nWHY IS THIS BETTER THAN SUBCLASSING?\n1. NO CLASS EXPLOSION — Instead of UpperTextHandler, LowerTextHandler, TrimTextHandler, you have one TextHandler class with different function delegates.\n2. RUNTIME FLEXIBILITY — Change behaviour after construction by reassigning the delegate field.\n3. COMPOSITION OVER INHERITANCE — Functions are composed, not inherited. This is the strategy pattern made trivial.\n4. TESTABILITY — Pass a test function delegate in unit tests.\n\nDYNAMIC FUNCTIONS AS DELEGATES\nYou can use dynamic functions with captures for custom behaviour:\n  prefix <- \">>>\"\n  custom <- TextHandler((prefix) is Processor as function\n    result:=? prefix + \" \" + text)\n  custom.process(\"hello\")    //Returns \">>> hello\"\n\nThis creates a one-off behaviour with captured context. In Java, this requires an anonymous inner class or lambda with a functional interface. In EK9, it is natural.\n\nIn Java, the strategy pattern requires defining an interface, implementing multiple classes, and wiring dependencies. In Go, you use function fields but without type hierarchies. In Python, you monkey-patch or use first-class functions but without type safety. EK9 makes the strategy pattern a first-class, type-safe, compile-time-verified pattern.\n\nSee Q51 for abstract functions. See Q52 for dynamic functions. See Q53 for variable capture. See Q55 for function delegates. See Q106 for traits. See Q109 for composition over inheritance. See Q214 for strategy pattern.","ek9Example":"defines module qa.strategypattern\n\n  defines function\n\n    Processor() as abstract\n      -> text as String\n      <- result as String?\n\n    UpperProcessor() extends Processor\n      -> text as String\n      <- result as String: text.upperCase()\n\n    LowerProcessor() extends Processor\n      -> text as String\n      <- result as String: text.lowerCase()\n\n  defines class\n\n    TextHandler\n      processor as Processor?\n\n      default private TextHandler()\n\n      TextHandler()\n        -> handler as Processor\n        this.processor: handler\n\n      process()\n        -> text as String\n        <- result as String: processor(text)\n\n      default operator ?\n\n  defines program\n\n    StrategyDemo()\n      stdout <- Stdout()\n\n      // Same class, different behaviour per instance\n      upper <- TextHandler(UpperProcessor)\n      lower <- TextHandler(LowerProcessor)\n\n      stdout.println(`upper: ${upper.process(\"hello\")}`)\n      stdout.println(`lower: ${lower.process(\"HELLO\")}`)\n\n      // Dynamic function with capture as strategy\n      prefix <- \">>>\"\n      customProcessor <- (prefix) is Processor as function\n        result:=? `${prefix} ${text}`\n      custom <- TextHandler(customProcessor)\n\n      stdout.println(`custom: ${custom.process(\"hello\")}`)","migrationContext":"Java: strategy pattern requires defining an interface, implementing it in separate classes, injecting via constructor, verbose boilerplate, anonymous inner classes before Java 8, lambdas require functional interface. Python: duck typing allows passing any callable, no type safety, monkey-patching can change behaviour but is fragile, no compile-time verification. JavaScript: pass function as property, no type safety, prototype manipulation for behaviour changes, 'this' binding confusion. Rust: trait objects (dyn Trait) for runtime polymorphism, Box<dyn Fn> for stored closures, complex lifetime management, no simple field-level strategy pattern. Go: function fields on structs, simple but no type hierarchies, no interface guarantee on the function type. C#: delegate fields, strategy pattern well-supported but requires explicit delegate type declarations. Kotlin: function type fields ((String) -> String), SAM conversion, but structural not nominal typing. Swift: closure properties, @escaping required, but structural not nominal. EK9: function delegate field typed as abstract function gives compile-time contract, per-instance behaviour without subclassing, dynamic functions with captures for one-off strategies, natural strategy pattern with type safety.","keywords":["beginner","behaviour","change","class","composition","constructor","delegate","field","first","function","inheritance","inject","instance","intro","migrate","pattern","start","strategy","subclass"],"primaryTopics":[],"typicalErrors":[{"error":"E07090","correct":"TextHandler\n      processor as Processor?","incorrect":"UpperTextHandler extends TextHandler","explanation":"TextHandler is not declared as 'as open' so it cannot be extended. Use composition with function delegate fields instead of creating subclasses. See ek9 -h E07090 for details."},{"error":"E08180","correct":"processor as Processor?","incorrect":"processor as Processor","explanation":"Class fields must be initialised at declaration. The '?' suffix creates the field in an uninitialised state that can be assigned later in the constructor. Without it, the field has no initial value. See ek9 -h E08180 for details."},{"error":"E02080","correct":"process()\n        -> text as String\n        <- result as String: processor(text)","incorrect":"processor()\n        -> text as String\n        <- result as String: processor(text)","explanation":"If TextHandler has a function delegate field 'processor' and also defines a method called 'processor()', the compiler reports E02080 DELEGATE_AND_METHOD_NAMES_CLASH. Calling processor() would be ambiguous — is it the method or the delegate invocation? Rename either the field or the method to avoid the clash. See ek9 -h E02080 for details."}],"companions":[]}
{"id":58,"category":"Getting Started","question":"How do generic functions work in EK9?","url":"https://ek9.io/qa/QA0058.html","alternatePhrasings":["How do I define a function with type parameters in EK9?","What are implicit super functions in EK9?","How does EK9 auto-create parameterised function supers?"],"answer":"EK9 functions can have type parameters using 'of type T' for single-parameter or 'of type (S, T)' for multi-parameter generics. Generic functions serve as templates that create concrete function types when instantiated with specific types.\n\nSINGLE TYPE PARAMETER\n  transformer() of type T as open\n    -> item as T\n    <- result as T?\n\nMULTI TYPE PARAMETER\n  mapper() of type (S, T) as open\n    -> item as S\n    <- result as T := T()\n\nThe 'as open' modifier allows these to be extended with concrete types.\n\nINSTANTIATING GENERIC FUNCTIONS\nCreate a concrete dynamic function from a generic template by specifying types:\n  intDoubler <- () is transformer of Integer as function\n    result:=? item * 2\n\n  intToString <- () is mapper of (Integer, String) as function\n    result: $item\n\nIMPLICIT SUPER GENERATION\nWhen you write '() is transformer of Integer as function', the compiler AUTOMATICALLY creates a concrete function type 'transformer of Integer' as the super type. You do not need to declare this type separately — it is generated implicitly from the generic template. This is unique to EK9.\n\nThe auto-generated super has the correct parameterised signature: 'transformer of Integer' has '-> item as Integer, <- result as Integer?'. This ensures type safety without boilerplate.\n\nCONSTRAINING TYPE PARAMETERS\nUse 'constrain by' to restrict which types can be used:\n  shapeHandler() of type T constrain by Shape as open\n    -> shape as T\n    <- result as Boolean := false\n\nWith the constraint, you can call methods defined on Shape within the function body. Without constraints, only standard operators are available.\n\nGENERIC FUNCTIONS WITH 'extends'\nBoth 'is' and 'extends' work:\n  otherDoubler <- () extends transformer of Integer as function\n    result:=? item + item\n\nNAMED GENERIC FUNCTION IMPLEMENTATIONS\n  doubleInteger() is transformer of Integer\n    -> item as Integer\n    <- result as Integer: item * 2\n\nIn Java, generics use type erasure and cannot create new types at runtime. In Rust, generics are monomorphised but closures cannot participate in generic hierarchies. In Go, generics (since 1.18) have no function type hierarchies. EK9's generic functions with implicit super generation provide type-safe, nominal, zero-boilerplate generic function hierarchies.\n\nSee Q51 for abstract functions. See Q52 for dynamic functions. See Q53 for variable capture in generic function instances. See Q54 for built-in generic function types (Predicate, Comparator, UnaryOperator). See Q55 for passing generic function instances as delegates. See Q89 for stream pipeline basics. See Q194 for generic classes. See Q195 for generic constraints. See Q235 for how generic function types are used as stream pipeline stages.","ek9Example":"defines module qa.genericfunction\n\n  defines function\n\n    transformer() of type T as abstract\n      -> item as T\n      <- result as T?\n\n    mapper() of type (S, T) as abstract\n      -> item as S\n      <- result as T?\n\n  defines program\n\n    GenericFunctionDemo()\n      stdout <- Stdout()\n\n      // Instantiate generic function with Integer\n      intDoubler <- () is transformer of Integer as function\n        result:=? item * 2\n\n      stdout.println(`doubled: ${intDoubler(21)}`)\n\n      // Multi-parameter generic — Integer to String\n      intToString <- () is mapper of (Integer, String) as function\n        result: $item\n\n      stdout.println(`mapped: ${intToString(42)}`)\n\n      // Using 'extends' instead of 'is'\n      intTripler <- () extends transformer of Integer as function\n        result:=? item * 3\n\n      stdout.println(`tripled: ${intTripler(10)}`)\n\n      // Store generic function instances in a list\n      transforms <- [intDoubler, intTripler]\n      for transform in transforms\n        stdout.println(`transform(5): ${transform(5)}`)","migrationContext":"Java: generics use type erasure (no runtime type information), functional interfaces can be generic but lambdas do not create named types, no implicit super generation, wildcard complexity (? extends T, ? super T). Python: no generics until 3.12, typing.Generic is runtime-invisible, no compile-time enforcement, no function type hierarchies. JavaScript: no generics, TypeScript has structural generics but no function type hierarchies. Rust: generics are monomorphised (good performance), impl Trait for generic returns, but closures cannot form generic type hierarchies, complex lifetime annotations with generics. Go: generics since 1.18, no function type hierarchies, limited type constraints. C#: generics with runtime type information, delegates can be generic, but no function type inheritance. Kotlin: generics similar to Java with reified keyword for some runtime info, no function type hierarchies, variance annotations (in/out). Swift: generics with associated types and protocols, no function type hierarchies. EK9: generic functions with 'of type T' or 'of type (S, T)', implicit super generation creates parameterised function types automatically, 'constrain by' for type bounds, 'as open' for extensibility, nominal typing throughout.","keywords":["beginner","constrain","first","function","generation","generic","implicit","instantiate","intro","migrate","open","parameter","parameterised","start","super","template","type"],"primaryTopics":["generic function","parameterized function"],"typicalErrors":[{"error":"E50100","correct":"intDoubler <- () is transformer of Integer as function","incorrect":"intDoubler <- () is transformer as function","explanation":"Implementing a generic function without specifying the type parameter triggers E50100 — type is generic but no parameters were supplied. You must specify the concrete type when implementing a generic function. See ek9 -h E50100 for details."},{"error":"E06020","correct":"intToString <- () is mapper of (Integer, String) as function","incorrect":"intToString <- () is mapper of Integer as function","explanation":"If a generic function has two type parameters, providing only one triggers E06020 — incorrect number of parameters supplied. Match the number of type arguments to the generic declaration. See ek9 -h E06020 for details."}],"companions":[]}
{"id":59,"category":"Getting Started","question":"How do I use functions in stream pipelines?","url":"https://ek9.io/qa/QA0059.html","alternatePhrasings":["How do dynamic functions work with EK9 stream pipelines?","How do I capture variables for use in a pipeline stage?","How do functions integrate with cat, map, and filter in EK9?"],"answer":"Dynamic functions with variable capture are essential for EK9 stream pipelines. They provide context to pipeline stages like 'map with' and 'filter by' without global state or side effects.\n\nBASIC PIPELINE WITH NAMED FUNCTIONS\n  cat items | map with transformer | filter by checker | collect as List of String\n\nEach stage uses a function reference. 'map with' applies a transformation function. 'filter by' uses a predicate function.\n\nCAPTURING CONTEXT FOR PIPELINE STAGES\nThe real power comes from dynamic functions that capture variables for use in pipeline stages:\n  names <- Dict() of (Integer, String)\n  names += DictEntry(1, \"Alice\")\n  names += DictEntry(2, \"Bob\")\n\n  idToName <- (names) is idMapper as function\n    result: names.getOrDefault(id, \"Unknown\")\n\n  resolved <- cat [1, 2, 3]\n    | map with idToName\n    | collect as List of String\n\nThe dynamic function 'idToName' captures the 'names' Dict and uses it inside the pipeline. Each pipeline element gets the captured context without global state.\n\nSTATEFUL PIPELINE FUNCTIONS\nDynamic functions can hold state across pipeline iterations — useful for accumulation, counting, or multi-element processing:\n  count <- 0\n  counter <- (count) is someAccumulator as function\n    count++\n    result:=? count\n\nThis is explicitly stated as a design feature: dynamic functions provide real power when building stream pipelines where you want to retain state as part of the pipeline process rather than depending on a single reduce at the end.\n\nINLINE FUNCTIONS IN PIPELINES\nUse inline syntax directly in the pipeline:\n  results <- cat keys\n    | map with (names) is idMapper (result: names.getOrDefault(id, \"Unknown\"))\n    | collect as List of String\n\nThis is compact but still nominally typed — the compiler verifies the function matches the pipeline stage requirements.\n\nFLATTEN FOR OPTIONAL RESULTS\nWhen a pipeline stage returns Optional values, use 'flatten' to extract set values and discard unset ones:\n  results <- cat keys\n    | map with lookupFunction\n    | flatten\n    | collect as List of String\n\nThis eliminates null checking in pipeline code.\n\nIn Java, streams use lambdas but capture is hidden and by reference. In Python, generators and comprehensions have no type safety. In Rust, iterators with closures require complex lifetime annotations. EK9 pipelines with dynamic function captures are type-safe, explicit, and maintain quality enforcement.\n\nSee Q51 for abstract functions used as pipeline stage types. See Q52 for dynamic functions. See Q53 for variable capture. See Q54 for Consumer patterns in pipeline callbacks. See Q45 for List operations. See Q46 for Dict operations. See Q89 for stream pipeline basics. See Q122 for collect as and custom aggregators. See Q235 for complete stream operations reference.","ek9Example":"defines module qa.functionpipeline\n\n  defines function\n\n    nameMapper() as abstract\n      -> identifier as Integer\n      <- result as String?\n\n  defines program\n\n    PipelineDemo()\n      stdout <- Stdout()\n\n      // Set up context data\n      names <- Dict() of (Integer, String)\n      names += DictEntry(1, \"Alice\")\n      names += DictEntry(2, \"Bob\")\n      names += DictEntry(3, \"Charlie\")\n\n      // Dynamic function captures Dict for pipeline use\n      lookup <- (names) is nameMapper as function\n        result: names.getOrDefault(identifier, \"Unknown\")\n\n      // Pipeline with captured context\n      ids <- [1, 2, 4, 3]\n      results <- cat ids\n        | map with lookup\n        | collect as List of String\n\n      for name in results\n        stdout.println(`resolved: ${name}`)\n\n      // Direct function call — same function works outside pipelines\n      stdout.println(`direct: ${lookup(2)}`)","migrationContext":"Java: Stream API with lambdas, hidden capture by reference, map/filter/reduce pattern, no explicit capture list, lambda quality not enforced, flatMap for Optional unwrapping. Python: generators, list comprehensions, map/filter built-ins, no type safety, closures capture by reference. JavaScript: Array.map/filter/reduce with arrow functions, no type safety, promise chains, hidden capture. Rust: Iterator trait with map/filter/collect, closures with complex lifetime annotations, move semantics for capture, turbofish syntax for type annotation. Go: no built-in stream pipelines, manual loops, channels for pipeline patterns, verbose. Kotlin: sequences with lambda chains, structural typing, inline functions for performance, no explicit capture. Swift: lazy sequences with closures, structural typing, no explicit capture list in pipeline context. EK9: stream pipelines with cat/map/filter/collect, dynamic functions with EXPLICIT by-value capture provide pipeline context, stateful functions for accumulation, inline syntax for compact stages, flatten for Optional unwrapping, type-safe and quality-enforced.","keywords":["anonymous","beginner","capture","cat","closure","collect","context","dynamic","filter","first","flatten","function","intro","map","migrate","pipeline","stage","start","stateful","stream"],"primaryTopics":["stream pipeline","pipe","functional pipeline"],"typicalErrors":[{"error":"E07880","correct":"| map with lookup","incorrect":"| call lookup","explanation":"Using 'call' instead of 'map with' for a function that takes a parameter and returns a value triggers E07830 — stream pipeline type mismatch. Use 'map with' for transformation functions. See ek9 -h E07830 for details."},{"error":"E50060","correct":"names.getOrDefault(identifier, \"Unknown\")","incorrect":"names.get(identifier, \"Unknown\")","explanation":"EK9 Dict does not have a get() method. Use getOrDefault(key, default) for safe access. Triggers E50060 — method not resolved. See ek9 -h Dict for the full API."}],"companions":[]}
{"id":60,"category":"Getting Started","question":"What is function dispatching in EK9?","url":"https://ek9.io/qa/QA0060.html","alternatePhrasings":["How does the dispatcher keyword work in EK9?","How do I dispatch on multiple argument types in EK9?","How does EK9 handle multiple dispatch?"],"answer":"EK9 has a 'dispatcher' keyword that enables runtime type-based dispatch on method parameters. This is like method overloading but resolved at RUNTIME based on the actual types of the arguments, not just the declared types.\n\nBASIC DISPATCHER\nMark a method as 'dispatcher' and provide overloaded implementations:\n  Formatter\n    format() as dispatcher\n      -> item as Any\n      <- result as String: $item\n\n    format()\n      -> item as Integer\n      <- result as String: \"Int: \" + $item\n\n    format()\n      -> item as String\n      <- result as String: \"Str: \" + item\n\nWhen you call 'formatter.format(someValue)', EK9 dispatches to the most specific overload based on the runtime type of 'someValue'. If no specific overload matches, the dispatcher base method (with 'Any' parameter) handles it.\n\nWHY IS THIS POWERFUL?\nIn Java, method overloading is resolved at COMPILE time based on declared types. If you have a variable typed as Object that holds an Integer, Java calls the Object overload. EK9 calls the Integer overload because it dispatches on the RUNTIME type.\n\nThis eliminates the visitor pattern. Instead of defining accept/visit methods across a class hierarchy, you write a single dispatcher method with overloads for each type.\n\nDISPATCHER WITH MULTIPLE PARAMETERS\nDispatchers can dispatch on multiple parameters:\n  intersect() as dispatcher\n    -> s1 as Shape, s2 as Shape\n    <- result as String: \"generic\"\n\n  intersect()\n    -> s1 as Circle, s2 as Circle\n    <- result as String: \"circle-circle\"\n\n  intersect()\n    -> s1 as Circle, s2 as Rectangle\n    <- result as String: \"circle-rectangle\"\n\nThis is true multiple dispatch — something only Julia and Common Lisp natively support among mainstream languages.\n\nDISPATCHER WITH TRAITS\nDispatchers work with trait types, using 'allow only' to restrict which types the dispatcher accepts. This enables safe dispatching over trait hierarchies.\n\nDISPATCHER IS PURE-COMPATIBLE\nDispatchers can be marked 'as pure dispatcher' for side-effect-free dispatching.\n\nIn Java, you need the visitor pattern (verbose, fragile). In Python, you use functools.singledispatch (limited). In Go, you use type switches. In Rust, you use match with enum variants. EK9's dispatcher is built into the language with compile-time verification of completeness.\n\nSee Q49 for defining functions. See Q51 for abstract functions. See Q56 for higher-order functions as an alternative dispatch mechanism. See Q105 for method dispatch in classes. See Q255 for cost-based method resolution in overloading. See Q600 for method overloading vs function dispatching comparison.","ek9Example":"defines module qa.dispatching\n\n  defines class\n\n    Shape as abstract\n      name() as abstract\n        <- rtn as String?\n      default operator ?\n\n    Circle is Shape\n      override name()\n        <- rtn as String: \"Circle\"\n\n    Square is Shape\n      override name()\n        <- rtn as String: \"Square\"\n\n    Triangle is Shape\n      override name()\n        <- rtn as String: \"Triangle\"\n\n    Renderer\n      render() as dispatcher\n        -> shape as Shape\n        <- result as String: \"Shape: \" + shape.name()\n\n      render()\n        -> shape as Circle\n        <- result as String: \"Rendered Circle\"\n\n      render()\n        -> shape as Square\n        <- result as String: \"Rendered Square\"\n\n  defines program\n\n    DispatchDemo()\n      stdout <- Stdout()\n\n      renderer <- Renderer()\n\n      // Dispatcher selects overload based on runtime type\n      circle <- Circle()\n      square <- Square()\n      triangle <- Triangle()\n\n      stdout.println(renderer.render(circle))\n      stdout.println(renderer.render(square))\n      // Triangle has no specific handler — falls through to base\n      stdout.println(renderer.render(triangle))","migrationContext":"Java: method overloading resolved at compile time (static dispatch), visitor pattern needed for runtime dispatch, verbose accept/visit boilerplate, instanceof checks are code smell. Python: functools.singledispatch for single-argument dispatch, limited to one parameter, no compile-time verification, runtime errors for missing handlers. JavaScript: no dispatch mechanism, manual typeof/instanceof checks, no type safety. Rust: match on enum variants, no multiple dispatch, trait methods for single dispatch, pattern matching is powerful but not multi-argument dispatch. Go: type switch for runtime dispatch, no multiple dispatch, no compile-time completeness check. C#: dynamic keyword for late binding, no multiple dispatch, visitor pattern needed. Kotlin: when with is checks for type matching, no multiple dispatch, smart casts help but are single-argument. Julia: built-in multiple dispatch as core language feature, most similar to EK9's dispatcher, but dynamically typed. Swift: no multiple dispatch, protocol-based single dispatch, switch with pattern matching. EK9: built-in 'dispatcher' keyword for runtime type-based dispatch, multiple dispatch on multiple parameters, compile-time verified, pure-compatible, eliminates visitor pattern, works with traits and class hierarchies.","keywords":["beginner","dispatch","dispatcher","first","function","handler","immutable","intro","migrate","multiple","overload","parameter","pattern","pure","resolve","runtime","sealed","side-effect","start","trait","type","visitor"],"primaryTopics":[],"typicalErrors":[{"error":"E05120","correct":"Circle is Shape\n      override name()\n        <- rtn as String: \"Circle\"","incorrect":"Circle is Shape\n      name()\n        <- rtn as String: \"Circle\"","explanation":"When implementing an abstract method from a parent class, the 'override' keyword is required. Without it, the compiler treats it as a new method rather than an implementation. See ek9 -h E05120 for details."}],"companions":[]}
{"id":61,"category":"Control Flow","question":"How do if/else statements work in EK9?","url":"https://ek9.io/qa/QA0061.html","alternatePhrasings":["How do I write a conditional in EK9?","What is the if statement syntax in EK9?","How do I test a condition in EK9?"],"answer":"EK9's if/else works like most languages but with a few differences: no parentheses around conditions, indentation-based blocks (no braces), and no 'elif' keyword (use 'else if' as two words).\n\nBASIC IF\nThe simplest form tests a condition and executes the indented body:\n  if temperature > 30\n    stdout.println(\"Hot day\")\nNo parentheses. No braces. Just condition and indented body.\n\nIF/ELSE\nAdd an else branch for the alternative path:\n  if balance > 0\n    status: \"positive\"\n  else\n    status: \"overdrawn\"\nThe else keyword sits at the same indentation as the if keyword.\n\nIF/ELSE-IF/ELSE\nChain conditions with 'else if' (two words, never 'elif'):\n  if score >= 90\n    grade: \"A\"\n  else if score >= 80\n    grade: \"B\"\n  else if score >= 70\n    grade: \"C\"\n  else\n    grade: \"F\"\nConditions are evaluated top to bottom. The first match wins.\n\nCONDITION TYPES\nAny expression that produces a Boolean can be a condition:\n  if ready                         single Boolean variable\n  if count > 0                     comparison\n  if name == \"Admin\"               equality\n  if age >= 18 and hasConsent      compound with 'and'\n  if isActive or isAdmin           compound with 'or'\n  if not finished                  negation\n\nALTERNATIVE KEYWORD: WHEN\nEK9 allows 'when' as an alternative to 'if'. They are interchangeable:\n  when temperature > 30\n    stdout.println(\"Hot day\")\nThis is purely stylistic. Use whichever reads better in context.\n\nWHAT IF/ELSE DOES NOT DO IN EK9\nEK9's if is a statement, not an expression. You cannot write:\n  result <- if condition then \"yes\" else \"no\"\nFor expression-based branching, use switch as an expression (see Q68).\n\nGUARDS AND MORE\nEK9's if supports guard variables that combine assignment with condition checking. This is covered in Q74. Guards are what make EK9's if truly powerful, but the basic form shown here works exactly as you would expect from any language.\n\nSee Q29 for how unset values interact with conditions. See Q30 for Boolean operators (and, or, xor, not). See Q62 for complex if/else-if chaining patterns. See Q72 for the 'when' keyword in other contexts. See Q74 for guard variables in if statements.","ek9Example":"defines module qa.flow.conditional\n\n  defines function\n\n    supplyTemperature() as pure\n      <- rtn <- 35\n\n    supplyBalance() as pure\n      <- rtn <- -50\n\n    supplyScore() as pure\n      <- rtn <- 85\n\n    supplyAge() as pure\n      <- rtn <- 25\n\n    supplyLicenseStatus() as pure\n      <- rtn <- true\n\n    supplyAdminStatus() as pure\n      <- rtn <- false\n\n    supplyOwnerStatus() as pure\n      <- rtn <- true\n\n    supplyFinishedStatus() as pure\n      <- rtn <- false\n\n  defines program\n\n    IfElseDemo()\n      stdout <- Stdout()\n\n      // === BASIC IF ===\n\n      temperature <- supplyTemperature()\n      temperatureThreshold <- 30\n      if temperature > temperatureThreshold\n        stdout.println(\"Hot day\")\n\n      // === IF/ELSE ===\n\n      balance <- supplyBalance()\n      status <- String()\n      if balance > 0\n        status: \"positive\"\n      else\n        status: \"overdrawn\"\n      stdout.println(`Balance status: ${status}`)\n\n      // === IF/ELSE-IF/ELSE ===\n\n      score <- supplyScore()\n      grade <- String()\n      excellentScore <- 90\n      goodScore <- 80\n      passingScore <- 70\n      if score >= excellentScore\n        grade: \"A\"\n      else if score >= goodScore\n        grade: \"B\"\n      else if score >= passingScore\n        grade: \"C\"\n      else\n        grade: \"F\"\n      stdout.println(`Score ${score} is grade ${grade}`)\n\n      // === COMPOUND CONDITIONS ===\n\n      age <- supplyAge()\n      hasLicense <- supplyLicenseStatus()\n      adultAge <- 18\n      if age >= adultAge and hasLicense\n        stdout.println(\"Can drive\")\n\n      isAdmin <- supplyAdminStatus()\n      isOwner <- supplyOwnerStatus()\n      if isAdmin or isOwner\n        stdout.println(\"Has access\")\n\n      // === NEGATION ===\n\n      finished <- supplyFinishedStatus()\n      if not finished\n        stdout.println(\"Still working\")\n\n      // === WHEN (alternative keyword) ===\n\n      hotThreshold <- 30\n      when temperature > hotThreshold\n        stdout.println(\"When says: hot day\")","migrationContext":"Java: if (condition) { } else if { } else { } with parentheses and braces required. Python: if/elif/else with colon and indentation, 'elif' keyword. Rust: if condition { } else if { } else { } with braces required, if is an expression. Go: if condition { } else if { } else { } with braces required, can declare variable in if. Kotlin: if/else is an expression, can return values. C++: if (condition) { } else if { } else { } with parentheses and braces. JavaScript: if (condition) { } else if { } else { } with parentheses and braces. C#: if (condition) { } else if { } else { } with parentheses and braces. Swift: if condition { } else if { } else { } no parentheses, braces required. EK9: if/else with indentation, no parentheses, no braces, no elif (use 'else if'), 'when' as alternative keyword, guard variables for safe null checking.","keywords":["boolean","branch","check","comparison","condition","conditional","control","else","flow","if","test","when"],"primaryTopics":["if else","if statement","conditional","branching"],"typicalErrors":[{"error":"E01072","correct":"if temperature > temperatureThreshold\n        stdout.println(\"Hot day\")","incorrect":"if temperature > temperatureThreshold\n        stdout.println(\"Hot day\")\n        return","explanation":"EK9 has no return statement. The keyword does not exist in the grammar. Use declared return variables and guard expressions instead. See ek9 -h E01072 for details."}],"companions":[]}
{"id":62,"category":"Control Flow","question":"How do I chain multiple if/else-if conditions?","url":"https://ek9.io/qa/QA0062.html","alternatePhrasings":["How does else-if work in EK9?","Why is there no elif in EK9?","How do I write multiple condition branches in EK9?"],"answer":"EK9 uses 'else if' as two separate words for chaining conditions. There is no 'elif' keyword (Python) or 'elsif' (Ruby/Perl). This is a deliberate choice for readability and consistency.\n\nBASIC CHAIN\nConditions are evaluated top to bottom. The first matching branch executes:\n  if score >= 90\n    grade: \"A\"\n  else if score >= 80\n    grade: \"B\"\n  else if score >= 70\n    grade: \"C\"\n  else if score >= 60\n    grade: \"D\"\n  else\n    grade: \"F\"\nOnly one branch ever executes. Once a condition matches, the remaining branches are skipped.\n\nCOMPOUND CONDITIONS IN CHAINS\nEach branch can use compound Boolean expressions:\n  if age < 13\n    category: \"child\"\n  else if age < 18\n    category: \"teenager\"\n  else if age < 65 and isEmployed\n    category: \"working adult\"\n  else if age >= 65\n    category: \"retired\"\n  else\n    category: \"adult\"\n\nNO ELIF KEYWORD\nSome developers coming from Python expect 'elif'. EK9 does not have this keyword. Using 'elif' will produce a compiler error. Always use 'else if' as two words.\n\nWHEN TO USE SWITCH INSTEAD\nIf you are matching a single variable against specific values, consider switch instead of a long if/else-if chain:\n  switch day\n    case \"Monday\"\n      action: \"start of week\"\n    case \"Friday\"\n      action: \"end of week\"\n    default\n      action: \"mid week\"\nSwitch is often clearer when testing one value against multiple possibilities. See Q63 for switch syntax. For comparison operators in switch cases (case < 12, case > 100), see Q70.\n\nSee Q61 for basic if/else syntax. See Q63 for switch as an alternative. See Q72 for 'when' as an alternative keyword.","ek9Example":"defines module qa.flow.chained.conditions\n\n  defines function\n\n    classifyAge() as pure\n      -> ageInput as Integer\n      <- rtn as Integer: ageInput\n\n    checkEmployment() as pure\n      -> employed as Boolean\n      <- rtn as Boolean: employed\n\n    classifyTemperature() as pure\n      -> celsius as Float\n      <- description as String: \"unknown\"\n\n      lowTemp <- 10.0\n      mildTemp <- 20.0\n      warmTemp <- 30.0\n      hotTemp <- 40.0\n\n      if celsius < 0.0\n        description: \"freezing\"\n      else if celsius < lowTemp\n        description: \"cold\"\n      else if celsius < mildTemp\n        description: \"cool\"\n      else if celsius < warmTemp\n        description: \"warm\"\n      else if celsius < hotTemp\n        description: \"hot\"\n      else\n        description: \"extreme\"\n\n  defines program\n\n    IfChainDemo()\n      stdout <- Stdout()\n\n      // === GRADE CLASSIFICATION ===\n\n      scores <- [95, 82, 73, 61, 45]\n      for score in scores\n        grade <- String()\n        excellent <- 90\n        good <- 80\n        average <- 70\n        belowAverage <- 60\n        if score >= excellent\n          grade: \"A\"\n        else if score >= good\n          grade: \"B\"\n        else if score >= average\n          grade: \"C\"\n        else if score >= belowAverage\n          grade: \"D\"\n        else\n          grade: \"F\"\n        stdout.println(`Score ${score} -> Grade ${grade}`)\n\n      // === TEMPERATURE CLASSIFICATION (function) ===\n\n      temperatures <- [Float(-5.0), Float(8.0), Float(15.0), Float(25.0), Float(38.0)]\n      for temp in temperatures\n        stdout.println(`${temp}C is ${classifyTemperature(temp)}`)\n\n      // === COMPOUND CONDITIONS ===\n\n      age <- classifyAge(30)\n      isEmployed <- checkEmployment(true)\n      category <- String()\n      teenAge <- 13\n      adultAge <- 18\n      seniorAge <- 65\n      if age < teenAge\n        category: \"child\"\n      else if age < adultAge\n        category: \"teenager\"\n      else if age < seniorAge and isEmployed\n        category: \"working adult\"\n      else if age >= seniorAge\n        category: \"retired\"\n      else\n        category: \"adult\"\n      stdout.println(`Age ${age}: ${category}`)","migrationContext":"Java: else if with braces, no special keyword. Python: elif keyword (single word). Rust: else if with braces, if is an expression so match often preferred. Go: else if with braces, no special keyword. Ruby: elsif keyword. Perl: elsif keyword. Kotlin: else if, or 'when' expression for multi-branch. C#: else if with braces. JavaScript: else if with braces. Swift: else if with braces. EK9: else if (two words), no elif/elsif keyword, indentation-based blocks.","keywords":["branch","cascade","chain","condition","conditions","control","elif","else","elsif","flow","if","migrate","multiple"],"primaryTopics":[],"typicalErrors":[{"error":"E01072","correct":"if score >= excellent\n          grade: \"A\"\n        else if score >= good\n          grade: \"B\"","incorrect":"if score >= excellent\n          return \"A\"","explanation":"EK9 has no return statement. Assign to a pre-declared variable in each branch instead. The compiler verifies all paths initialise the variable. See ek9 -h E01072 for details."}],"companions":[]}
{"id":63,"category":"Control Flow","question":"How does switch/case work in EK9?","url":"https://ek9.io/qa/QA0063.html","alternatePhrasings":["What is EK9's equivalent of switch or match?","How do I match a value against multiple options in EK9?","How does case matching work in EK9?"],"answer":"EK9's switch matches a value against multiple cases. It looks familiar but has important differences from other languages: no break statements needed, no fallthrough between cases, and the ability to match multiple values per case.\n\nBASIC SWITCH\nMatch a value against literal cases:\n  switch status\n    case 1\n      message: \"active\"\n    case 2\n      message: \"inactive\"\n    case 3\n      message: \"pending\"\n    default\n      message: \"unknown\"\nEach case runs its body and stops. There is no fallthrough and no break keyword.\n\nSTRING MATCHING\nSwitch works with any type that supports equality:\n  switch command\n    case \"start\"\n      doStart()\n    case \"stop\"\n      doStop()\n    default\n      showHelp()\n\nDEFAULT CASE\nThe default block handles any value not matched by a case. It is optional but recommended for safety:\n  switch direction\n    case \"north\"\n      y: y + 1\n    case \"south\"\n      y: y - 1\n    default\n      stdout.println(\"Unhandled direction\")\n\nNO BREAK NEEDED\nIn Java and C, forgetting 'break' causes fallthrough bugs. EK9 eliminates this entirely. Each case is self-contained. There is no break keyword in EK9 (see Q144 for why).\n\nNO FALLTHROUGH\nIf you need multiple values to execute the same code, use comma-separated case values instead of fallthrough (see Q69):\n  switch day\n    case \"Saturday\", \"Sunday\"\n      type: \"weekend\"\n    default\n      type: \"weekday\"\n\nMORE POWER\nSwitch in EK9 goes far beyond simple literal matching:\n- Return values as an expression (see Q68)\n- Multiple values per case (see Q69)\n- Comparison operators like case < 12 (see Q70)\n- Pattern matching with regex (see Q71)\n- Alternative keywords given/when (see Q72)\n- Exhaustive enum matching (see Q73)\n- Guard variables for safe access (see Q74)\n\nSee Q61 for if/else as an alternative. See Q62 for when if/else-if chains might be better. See Q68 for switch expression. See Q73 for enum switch.","ek9Example":"defines module qa.flow.branching\n\n  defines function\n\n    supplyStatusCode()\n      <- rtn <- 2\n\n    supplyCommand()\n      <- rtn <- \"greet\"\n\n    supplyDay()\n      <- rtn <- \"Saturday\"\n\n  defines program\n\n    SwitchDemo()\n      stdout <- Stdout()\n\n      // === BASIC SWITCH WITH INTEGERS ===\n\n      statusCode <- supplyStatusCode()\n      message <- String()\n      switch statusCode\n        case 1\n          message: \"active\"\n        case 2\n          message: \"inactive\"\n        case 3\n          message: \"pending\"\n        default\n          message: \"unknown\"\n      stdout.println(`Status ${statusCode}: ${message}`)\n\n      // === SWITCH WITH STRINGS ===\n\n      command <- supplyCommand()\n      switch command\n        case \"greet\"\n          stdout.println(\"Hello!\")\n        case \"farewell\"\n          stdout.println(\"Goodbye!\")\n        default\n          stdout.println(\"Unknown command\")\n\n      // === MULTIPLE VALUES PER CASE ===\n\n      day <- supplyDay()\n      dayType <- String()\n      switch day\n        case \"Monday\", \"Tuesday\", \"Wednesday\", \"Thursday\", \"Friday\"\n          dayType: \"weekday\"\n        case \"Saturday\", \"Sunday\"\n          dayType: \"weekend\"\n        default\n          dayType: \"invalid\"\n      stdout.println(`${day} is a ${dayType}`)\n\n      // === SWITCH IN A LOOP ===\n\n      codes <- [1, 2, 3, 4, 5]\n      for code in codes\n        label <- String()\n        switch code\n          case 1, 2\n            label: \"low\"\n          case 3\n            label: \"medium\"\n          case 4, 5\n            label: \"high\"\n          default\n            label: \"off scale\"\n        stdout.println(`Code ${code} -> ${label}`)","migrationContext":"Java: switch with break required (fallthrough by default), switch expressions since Java 14 with arrow syntax. Python: match/case since 3.10, structural pattern matching (different from simple value matching). Rust: match is exhaustive, is an expression, pattern matching with destructuring. Go: switch with no fallthrough by default (opposite of C/Java), implicit break. C/C++: switch with break required, fallthrough by default, major bug source. Kotlin: when expression replaces switch, no fallthrough, exhaustive with sealed classes. C#: switch with break required, switch expressions since C# 8. JavaScript: switch with break required, fallthrough by default. Swift: switch is exhaustive, no fallthrough, pattern matching. EK9: switch with no break keyword, no fallthrough, comma-separated multi-case values, comparison operators in cases, given/when alternative keywords.","keywords":["branch","break","case","condition","control","default","fallthrough","flow","given","literal","match","migrate","switch","value"],"primaryTopics":["switch","switch case","match"],"typicalErrors":[{"error":"E01070","correct":"switch statusCode\n        case 1\n          message: \"active\"\n        case 2\n          message: \"inactive\"","incorrect":"switch statusCode\n        case 1\n          message: \"active\"\n          break\n        case 2\n          message: \"inactive\"\n          break","explanation":"EK9 has no break statement. Switch cases do not fall through, so break is unnecessary and does not exist in the grammar. Each case is self-contained. See ek9 -h E01070 for details."},{"error":"E01072","correct":"switch command\n        case \"greet\"\n          stdout.println(\"Hello!\")\n        case \"farewell\"\n          stdout.println(\"Goodbye!\")","incorrect":"switch command\n        case \"greet\"\n          stdout.println(\"Hello!\")\n          return\n        case \"farewell\"\n          stdout.println(\"Goodbye!\")\n          return","explanation":"EK9 has no return statement. In a program, execution flows naturally to the end. In functions, use a declared return variable instead. See ek9 -h E01072 for details."}],"companions":[]}
{"id":64,"category":"Control Flow","question":"How do I write a for loop over a range of numbers?","url":"https://ek9.io/qa/QA0064.html","alternatePhrasings":["How do I loop over a range of numbers in EK9?","What is the for loop syntax for counting in EK9?","How do I iterate from 1 to 10 in EK9?","How do range-based for loops work in EK9?","What are the basic loop constructs in EK9?"],"answer":"EK9 has a for-range loop that iterates over a numeric range using the '...' (ellipsis) operator. The range is inclusive on both ends.\n\nBASIC INTEGER RANGE\nCount from 1 to 10 (inclusive):\n  for i in 1 ... 10\n    stdout.println($i)\nThe variable i is implicitly declared as Integer. Both start and end values are included.\n\nCUSTOM STEP WITH BY\nUse 'by' to specify the step size:\n  for i in 0 ... 20 by 5\n    stdout.println($i)\nThis prints 0, 5, 10, 15, 20. The step can be any positive integer.\n\nDESCENDING RANGE\nCount downward by specifying a negative step:\n  for i in 10 ... 0 by -1\n    stdout.println($i)\nThis counts from 10 down to 0. The negative step is required for descending ranges.\n\nEVEN NUMBERS\nCombine start value and step:\n  for i in 2 ... 20 by 2\n    stdout.println($i)\nPrints 2, 4, 6, 8, 10, 12, 14, 16, 18, 20.\n\nVARIABLE BOUNDS\nThe start and end values can be variables or expressions:\n  limit <- 100\n  for i in 1 ... limit\n    total: total + i\n\nFLOAT RANGES\nFor-range works with Float values:\n  for f in 0.0 ... 1.0 by 0.25\n    stdout.println($f)\nPrints 0.0, 0.25, 0.5, 0.75, 1.0.\n\nDURATION AND TIME RANGES\nFor-range also works with Duration and Time types, making it easy to iterate over time intervals:\n  thirtyMinutes <- PT30M\n  start <- PT0S\n  end <- PT2H\n  for d in start ... end by thirtyMinutes\n    stdout.println($d)\nSee Q31 for Date/Time types and Q32 for Duration.\n\nNO BREAK\nEK9 has no break statement. For-range loops always run from start to end. If you need to stop early, use a stream pipeline with 'head' instead (see Q125). For-range can also return values as an expression (see Q80).\n\nSee Q39 for Integer and Float types. See Q65 for iterating over collections (for-in). See Q80 for using for-range as an expression. See Q144 for why there is no break. See Q89 for stream pipelines as loop alternatives. See Q125 for head as the only early exit mechanism. See Q83 for loop expression overview. See Q283 for retry logic using for-range.","ek9Example":"defines module qa.flow.range.loop\n\n  defines program\n\n    ForRangeDemo()\n      stdout <- Stdout()\n\n      // === BASIC INTEGER RANGE ===\n\n      stdout.println(\"Counting 1 to 5:\")\n      for i in 1 ... 5\n        stdout.println(`  ${i}`)\n\n      // === CUSTOM STEP ===\n\n      stdout.println(\"Even numbers 2 to 10:\")\n      for i in 2 ... 10 by 2\n        stdout.println(`  ${i}`)\n\n      // === DESCENDING RANGE ===\n\n      stdout.println(\"Countdown 5 to 1:\")\n      for i in 5 ... 1 by -1\n        stdout.println(`  ${i}`)\n\n      // === SUM WITH ACCUMULATION ===\n\n      total <- 0\n      for i in 1 ... 100\n        total: total + i\n      stdout.println(`Sum 1..100: ${total}`)\n\n      // === VARIABLE BOUNDS ===\n\n      start <- 3\n      end <- 7\n      stdout.println(`Range ${start} to ${end}:`)\n      for i in start ... end\n        stdout.println(`  ${i}`)\n\n      // === FLOAT RANGE ===\n\n      stdout.println(\"Float range 0.0 to 1.0:\")\n      for f in 0.0 ... 1.0 by 0.25\n        stdout.println(`  ${f}`)\n\n      // === NESTED RANGES ===\n\n      stdout.println(\"Multiplication table (1-3):\")\n      for row in 1 ... 3\n        for col in 1 ... 3\n          stdout.println(`  ${row} x ${col} = ${row * col}`)","migrationContext":"Java: for (int i = 0; i < 10; i++) with C-style syntax, verbose. Python: for i in range(1, 11) with exclusive end, range() function. Rust: for i in 1..=10 with ..= for inclusive, .. for exclusive. Go: for i := 0; i < 10; i++ with C-style syntax. C++: for (int i = 0; i < 10; ++i) with C-style syntax. Kotlin: for (i in 1..10) with .. operator, downTo for descending, step for custom increment. Swift: for i in 1...10 with ... for inclusive, ..< for exclusive. C#: for (int i = 0; i < 10; i++) with C-style syntax, Enumerable.Range(). EK9: for i in 1 ... 10 with ... operator (inclusive both ends), 'by' keyword for step, works with Integer, Float, Duration, and Time types.","keywords":["ascending","branch","by","condition","control","count","descending","ellipsis","float","flow","for","integer","iterate","loop","number","range","step"],"primaryTopics":["for loop","for range","counting loop","loop over numbers"],"typicalErrors":[{"error":"E01070","correct":"for i in 1 ... 5\n        stdout.println(`  ${i}`)","incorrect":"for i in 1 ... 5\n        stdout.println(`  ${i}`)\n        if i == 3\n          break","explanation":"EK9 has no break statement. For-range loops always run from start to end. Use stream pipelines with head to limit items instead. See ek9 -h E01070 for details."},{"error":"E01071","correct":"for i in 2 ... 10 by 2\n        stdout.println(`  ${i}`)","incorrect":"for i in 2 ... 10 by 2\n        if i == 6\n          continue\n        stdout.println(`  ${i}`)","explanation":"EK9 has no continue statement. Use stream pipelines with filter to skip unwanted items instead of continue. See ek9 -h E01071 for details."}],"companions":[]}
{"id":65,"category":"Control Flow","question":"How do I use a for loop to iterate over a collection?","url":"https://ek9.io/qa/QA0065.html","alternatePhrasings":["How do I loop through a list in EK9?","How does for-each work in EK9?","How do I iterate over a dictionary in EK9?","How do collection-based for loops work in EK9?","How do I use a for loop to iterate over items?"],"answer":"EK9's for-in loop iterates over any collection or iterable. The syntax is 'for item in collection'. This is the EK9 equivalent of Java's enhanced for, Python's for-in, and Rust's for-in.\n\nLIST ITERATION\nIterate over each element in a list:\n  fruits <- [\"apple\", \"banana\", \"cherry\"]\n  for fruit in fruits\n    stdout.println(fruit)\nThe loop variable 'fruit' is implicitly declared with the element type.\n\nDICT ITERATION\nIterate over dictionary entries:\n  ages <- {\"Alice\": 30, \"Bob\": 25}\n  for entry in ages\n    stdout.println($entry)\nEach entry is a DictEntry with key and value properties. See Q46 for Dict details.\n\nITERATOR PATTERN\nYou can also iterate using an explicit iterator:\n  items <- [\"one\", \"two\", \"three\"]\n  iter <- items.iterator()\n  while iter?\n    stdout.println(iter.next())\nThe ? operator checks if the iterator has more items (isSet). See Q66 for while loop details.\n\nFOR-IN VS FOR-RANGE\nFor-in iterates over a collection's elements. For-range iterates over a numeric range:\n  for item in myList        for-in: each element\n  for i in 1 ... 10         for-range: each number\nUse for-in when you have a collection. Use for-range when you need a counting loop (see Q64).\n\nWORKS WITH ANY ITERABLE\nFor-in works with any type that provides an iterator. This includes List, Dict, and any user-defined type that implements the iterator pattern.\n\nNO BREAK OR CONTINUE\nFor-in loops always process every element. There is no break to stop early and no continue to skip elements. If you need to filter or limit items, use a stream pipeline instead:\n  cat fruits | filter by isLong > stdout\nSee Q86 for early exit alternatives and Q87 for skip alternatives.\n\nFor-in can also return values as an expression (see Q80). Guard variables can be used in for-in (see Q74).\n\nSee Q45 for List type details. See Q46 for Dict type details. See Q64 for for-range loops. See Q89 for stream pipelines as alternatives to for-in loops. See Q120 for sorting streams. See Q125 for head as the only early exit mechanism.","ek9Example":"defines module qa.flow.collection.loop\n\n  defines function\n\n    isLong() as pure\n      -> text as String\n      <- rtn <- false\n      expectedCount <- 4\n      rtn: length text > expectedCount\n\n  defines program\n\n    ForInDemo()\n      stdout <- Stdout()\n\n      // === LIST ITERATION ===\n\n      fruits <- [\"apple\", \"banana\", \"cherry\", \"date\"]\n      stdout.println(\"Fruits:\")\n      for fruit in fruits\n        stdout.println(`  ${fruit}`)\n\n      // === LIST OF NUMBERS ===\n\n      numbers <- [10, 20, 30, 40, 50]\n      total <- 0\n      for num in numbers\n        total: total + num\n      stdout.println(`Sum: ${total}`)\n\n      // === DICT ITERATION ===\n\n      ages <- {\"Alice\": 30, \"Bob\": 25, \"Charlie\": 35}\n      stdout.println(\"Ages:\")\n      for entry in ages\n        stdout.println(`  ${entry}`)\n\n      // === NESTED ITERATION ===\n\n      matrix <- [[1, 2, 3], [4, 5, 6], [7, 8, 9]]\n      stdout.println(\"Matrix:\")\n      for row in matrix\n        for item in row\n          stdout.println(`  ${item}`)\n\n      // === STREAM ALTERNATIVE (filter) ===\n\n      stdout.println(\"Long fruit names (>4 chars):\")\n      cat fruits | filter by isLong > stdout","migrationContext":"Java: for (String item : list) enhanced for loop, or list.forEach(item -> ...) with lambda. Python: for item in list with direct iteration, enumerate() for index+value. Rust: for item in &list with borrowing semantics, .iter() for references, .into_iter() for ownership. Go: for _, item := range list with blank identifier for index, for k, v := range map for maps. C++: for (auto& item : list) range-based for since C++11. Kotlin: for (item in list) with direct iteration, forEachIndexed for index+value. C#: foreach (var item in list) with IEnumerable. JavaScript: for (const item of list) with for-of, .forEach() method. Swift: for item in list with direct iteration. EK9: for item in list with direct iteration, works with List, Dict, and any iterable type, no break/continue.","keywords":["branch","collection","condition","control","dict","dictionary","each","element","entry","flow","for","in","iterate","iterator","list","loop","migrate"],"primaryTopics":["for loop","for in","iterate collection","loop over list","for each"],"typicalErrors":[{"error":"E01070","correct":"for fruit in fruits\n        stdout.println(`  ${fruit}`)","incorrect":"for fruit in fruits\n        if fruit == \"cherry\"\n          break\n        stdout.println(`  ${fruit}`)","explanation":"EK9 has no break statement. For-in loops always process every element. Use stream pipelines with head to stop early. See ek9 -h E01070 for details."},{"error":"E01071","correct":"for num in numbers\n        total: total + num","incorrect":"for num in numbers\n        if num == 0\n          continue\n        total: total + num","explanation":"EK9 has no continue statement. Use filter in a stream pipeline to skip unwanted items, or use an if block inside the loop body. See ek9 -h E01071 for details."}],"companions":[]}
{"id":66,"category":"Control Flow","question":"How does the while loop work?","url":"https://ek9.io/qa/QA0066.html","alternatePhrasings":["What is the while loop syntax in EK9?","How do I write a condition-based loop in EK9?","How do I loop while a condition is true in EK9?","How does while work in EK9?"],"answer":"EK9's while loop repeatedly executes a body as long as a Boolean condition is true. The condition is checked before each iteration, so the body may never execute if the condition is initially false.\n\nBASIC WHILE\nLoop while a condition holds:\n  counter <- 0\n  while counter < 5\n    stdout.println($counter)\n    counter: counter + 1\nThis prints 0, 1, 2, 3, 4. The condition 'counter < 5' is checked before each iteration.\n\nITERATOR PATTERN\nA common use of while is consuming an iterator:\n  iter <- items.iterator()\n  while iter?\n    stdout.println(iter.next())\nThe ? operator checks if the iterator is set (has more items). This continues until the iterator is exhausted.\n\nCOUNTER ACCUMULATION\nBuild up a result across iterations:\n  sum <- 0\n  n <- 1\n  while n <= 100\n    sum: sum + n\n    n: n + 1\nThis computes the sum of 1 to 100.\n\nCONDITION IS A BOOLEAN EXPRESSION\nAny expression producing a Boolean works as the condition:\n  while not finished and retries < maxRetries\n    attempt()\n    retries: retries + 1\n\nWHILE VS FOR-RANGE\nUse for-range when you know the number of iterations:\n  for i in 1 ... 10\nUse while when the number of iterations depends on a runtime condition:\n  while hasMoreData()\n\nWHILE VS FOR-IN\nUse for-in to iterate over a collection directly:\n  for item in items\nUse while with an iterator when you need more control:\n  while iter?\n\nNO BREAK\nThere is no break statement to exit a while loop early. Use guards or restructure the condition instead. See Q144 for why break was removed and Q125 for alternatives.\n\nWhile loops support guard variables (see Q74) and can return values as expressions (see Q81).\n\nSee Q61 for if statements. See Q64 for for-range loops. See Q65 for for-in loops. See Q67 for do-while loops. See Q89 for stream pipelines as loop alternatives. See Q125 for head as the only early exit mechanism. See Q287 for process-until-done patterns with while.","ek9Example":"defines module qa.flow.condition.loop\n\n  defines function\n\n    supplyStart() as pure\n      -> initial as Integer\n      <- rtn as Integer: initial\n\n    supplyBoolean() as pure\n      -> initial as Boolean\n      <- rtn as Boolean: initial\n\n  defines program\n\n    WhileDemo()\n      stdout <- Stdout()\n\n      // === BASIC WHILE ===\n\n      counter <- supplyStart(0)\n      loopLimit <- 5\n      while counter < loopLimit\n        stdout.println(`Counter: ${counter}`)\n        counter: counter + 1\n\n      // === SUM ACCUMULATION ===\n\n      sum <- 0\n      n <- supplyStart(1)\n      maxIterations <- 10\n      while n <= maxIterations\n        sum: sum + n\n        n: n + 1\n      stdout.println(`Sum 1..10: ${sum}`)\n\n      // === ITERATOR PATTERN ===\n\n      names <- [\"Alice\", \"Bob\", \"Charlie\"]\n      iter <- names.iterator()\n      stdout.println(\"Names via iterator:\")\n      while iter?\n        stdout.println(`  ${iter.next()}`)\n\n      // === COUNTDOWN ===\n\n      remaining <- supplyStart(3)\n      while remaining > 0\n        stdout.println(`${remaining}...`)\n        remaining: remaining - 1\n      stdout.println(\"Done!\")\n\n      // === COMPOUND CONDITION ===\n\n      attempts <- 0\n      maxAttempts <- 5\n      succeeded <- supplyBoolean(false)\n      maxRetries <- 3\n      while not succeeded and attempts < maxAttempts\n        attempts: attempts + 1\n        if attempts == maxRetries\n          succeeded: true\n      stdout.println(`Succeeded after ${attempts} attempts`)","migrationContext":"Java: while (condition) { } with parentheses and braces required. Python: while condition: with colon and indentation, break/continue available. Rust: while condition { } with braces, loop for infinite loops, break/continue available. Go: for condition { } uses for keyword for while loops, no while keyword. C/C++: while (condition) { } with parentheses and braces. Kotlin: while (condition) { } with parentheses and braces, break/continue available. C#: while (condition) { } with parentheses and braces. JavaScript: while (condition) { } with parentheses and braces. Swift: while condition { } no parentheses, braces required. EK9: while condition with indentation, no parentheses, no braces, no break/continue, guard variables for safe assignment-and-check.","keywords":["basic","boolean","branch","condition","control","counter","flow","iterate","iterator","loop","repeat","simple","while"],"primaryTopics":["while loop","while","loop"],"typicalErrors":[{"error":"E07390","correct":"while counter < loopLimit\n        stdout.println(`Counter: ${counter}`)\n        counter: counter + 1","incorrect":"while true\n        stdout.println(`Counter: ${counter}`)\n        counter: counter + 1\n        if counter >= loopLimit\n          break","explanation":"EK9 has no break statement. Structure the loop condition to control termination rather than using an infinite loop with break. See ek9 -h E07390 for details."}],"companions":[]}
{"id":67,"category":"Control Flow","question":"How does do-while work in EK9?","url":"https://ek9.io/qa/QA0067.html","alternatePhrasings":["How do I write a loop that runs at least once in EK9?","What is the do-while syntax in EK9?","How do I loop with the condition at the end in EK9?"],"answer":"EK9's do-while loop executes the body first, then checks the condition. This guarantees the body runs at least once, unlike a regular while loop that may not execute at all.\n\nBASIC DO-WHILE\nThe body executes before the condition is checked:\n  counter <- 0\n  do\n    stdout.println($counter)\n    counter: counter + 1\n  while counter < 5\nThis prints 0, 1, 2, 3, 4. The body executes first, then 'counter < 5' is checked.\n\nAT-LEAST-ONCE GUARANTEE\nEven if the condition is false from the start, the body still runs once:\n  threshold <- 100\n  do\n    stdout.println(\"Runs once\")\n  while threshold < 10\nThe body prints 'Runs once' even though 100 is not less than 10.\n\nWHEN TO USE DO-WHILE\nUse do-while when you need at least one execution before checking the condition:\n- Reading user input (try once, then check if valid)\n- Processing a batch (do one iteration, check if more)\n- Retry logic (attempt once, check if succeeded)\n\nDO-WHILE VS WHILE\nwhile: Checks BEFORE each iteration. Body may never execute.\ndo-while: Checks AFTER each iteration. Body always runs at least once.\n\n  // while: might not run\n  while hasData()\n    process()\n\n  // do-while: always runs once\n  do\n    process()\n  while hasMoreData()\n\nNO BREAK\nLike all loops in EK9, do-while has no break statement. Structure your condition to control when the loop ends. See Q144 for why break was removed.\n\nDo-while supports guard variables (see Q74) and can return values as an expression (see Q81).\n\nSee Q66 for while loops. See Q64 for for-range loops. See Q65 for for-in loops. See Q89 for stream pipelines as loop alternatives. See Q125 for head as the only early exit mechanism.","ek9Example":"defines module qa.flow.postcondition.loop\n\n  defines function\n\n    supplyStart() as pure\n      -> initial as Integer\n      <- rtn as Integer: initial\n\n  defines program\n\n    DoWhileDemo()\n      stdout <- Stdout()\n\n      // === BASIC DO-WHILE ===\n\n      counter <- supplyStart(0)\n      loopLimit <- 5\n      do\n        stdout.println(`Counter: ${counter}`)\n        counter: counter + 1\n      while counter < loopLimit\n\n      // === AT-LEAST-ONCE GUARANTEE ===\n\n      // Even though 100 > 10, body runs once\n      threshold <- supplyStart(100)\n      ran <- false\n      maxCount <- 10\n      do\n        ran: true\n      while threshold < maxCount\n      stdout.println(`Ran at least once: ${ran}`)\n\n      // === ACCUMULATION ===\n\n      sum <- 0\n      n <- supplyStart(1)\n      maxIterations <- 10\n      do\n        sum: sum + n\n        n: n + 1\n      while n <= maxIterations\n      stdout.println(`Sum 1..10: ${sum}`)\n\n      // === BUILDING A STRING ===\n\n      built <- String()\n      i <- supplyStart(1)\n      countdown <- 5\n      do\n        if length built > 0\n          built: built + \", \"\n        built: built + $i\n        i: i + 1\n      while i <= countdown\n      stdout.println(`Built: ${built}`)","migrationContext":"Java: do { } while (condition); with braces, parentheses, and semicolon required. C/C++: do { } while (condition); same syntax as Java. Python: no do-while construct, use while True with break instead. Rust: no do-while, use loop { if !condition { break } } instead. Go: no do-while, use for { if !condition { break } } instead. Kotlin: do { } while (condition) similar to Java but no semicolon. C#: do { } while (condition); same as Java. JavaScript: do { } while (condition); same as Java. Swift: repeat { } while condition with 'repeat' keyword instead of 'do'. EK9: do ... while condition with indentation, no braces, no parentheses, no semicolon, no break available, guard variables supported.","keywords":["atleast","body","branch","condition","control","do","first","flow","loop","once","post","repeat","while"],"primaryTopics":["do while","do while loop"],"typicalErrors":[{"error":"E07390","correct":"do\n        stdout.println(`Counter: ${counter}`)\n        counter: counter + 1\n      while counter < loopLimit","incorrect":"do\n        stdout.println(`Counter: ${counter}`)\n        counter: counter + 1\n        if counter >= loopLimit\n          break\n      while true","explanation":"EK9 has no break statement. Place the real termination condition in the while clause rather than using break inside the body. See ek9 -h E07390 for details."}],"companions":[]}
{"id":68,"category":"Control Flow","question":"Can switch return a value (switch as expression)?","url":"https://ek9.io/qa/QA0068.html","alternatePhrasings":["How do I use switch as an expression in EK9?","How do I assign a value from a switch statement?","How does switch expression work in EK9?"],"answer":"Yes. EK9's switch can return a value by using the expression form. You declare a return variable inside the switch, each case assigns to it, and the whole switch becomes an expression that can be assigned to a variable.\n\nBASIC SWITCH EXPRESSION\nAssign the result of a switch:\n  dayName <- switch dayNumber\n    <- rtn as String?\n    case 1\n      rtn: \"Monday\"\n    case 2\n      rtn: \"Tuesday\"\n    default\n      rtn: \"unknown\"\nThe outer '<- dayName' captures the switch result. The inner '<- rtn as String?' declares the return variable. Each case assigns to 'rtn', and its final value is returned.\n\nRETURN VARIABLE DECLARATION\nThe return variable appears right after the switch line, indented:\n  result <- switch controlValue\n    <- rtn as String?\n    case ...\nThe '<- rtn as String?' declares a variable named 'rtn' of type String, initially unset. You can also initialise it: '<- rtn <- String()' or '<- rtn as String: \"default\"'.\n\nNO RETURN STATEMENT\nEK9 has no 'return' keyword. The value is returned implicitly through the declared return variable. Every path (all cases plus default) should set the variable.\n\nBOOLEAN SWITCH AS TERNARY\nA Boolean switch expression works as a ternary alternative:\n  label <- switch isEnabled\n    <- rtn as String?\n    case true\n      rtn: \"ON\"\n    default\n      rtn: \"OFF\"\nThis replaces the ternary operator from other languages.\n\nEK9 also provides a dedicated ternary syntax for simple cases:\n  label <- isEnabled <- \"ON\" else \"OFF\"\n\nSTATEMENT VS EXPRESSION\nThe statement form assigns to pre-declared variables (see Q63). The expression form creates a new variable from the result. Use expression form when the switch's purpose is to compute a single value.\n\nSwitch expressions work with all the same features: multiple case values (see Q69), comparison operators (see Q70), pattern matching (see Q71), given/when keywords (see Q72), and exhaustive enum matching (see Q73).\n\nSee Q63 for basic switch statement form. See Q61 for if/else as an alternative.","ek9Example":"defines module qa.flow.switch.expression\n\n  defines function\n\n    supplyDay() as pure\n      -> dayInput as Integer\n      <- rtn as Integer: dayInput\n\n    supplyEnabled() as pure\n      -> enabled as Boolean\n      <- rtn as Boolean: enabled\n\n    supplyCommand() as pure\n      -> cmd as String\n      <- rtn as String: cmd\n\n    supplyPriority() as pure\n      -> level as Integer\n      <- rtn as Integer: level\n\n  defines program\n\n    SwitchExpressionDemo()\n      stdout <- Stdout()\n\n      // === BASIC SWITCH EXPRESSION ===\n\n      dayNumber <- supplyDay(3)\n      dayName <- switch dayNumber\n        <- rtn as String?\n        case 1\n          rtn: \"Monday\"\n        case 2\n          rtn: \"Tuesday\"\n        case 3\n          rtn: \"Wednesday\"\n        case 4\n          rtn: \"Thursday\"\n        case 5\n          rtn: \"Friday\"\n        default\n          rtn: \"weekend or invalid\"\n      stdout.println(`Day ${dayNumber}: ${dayName}`)\n\n      // === BOOLEAN SWITCH AS TERNARY ALTERNATIVE ===\n\n      isEnabled <- supplyEnabled(true)\n      statusLabel <- switch isEnabled\n        <- rtn as String?\n        case true\n          rtn: \"ON\"\n        default\n          rtn: \"OFF\"\n      stdout.println(`Status: ${statusLabel}`)\n\n      // === STRING SWITCH EXPRESSION ===\n\n      command <- supplyCommand(\"start\")\n      response <- switch command\n        <- rtn as String?\n        case \"start\"\n          rtn: \"Starting service...\"\n        case \"stop\"\n          rtn: \"Stopping service...\"\n        case \"restart\"\n          rtn: \"Restarting service...\"\n        default\n          rtn: \"Unknown command\"\n      stdout.println(response)\n\n      // === ASSIGNING EXPRESSION RESULT ===\n\n      priority <- supplyPriority(2)\n      urgencyLabel <- switch priority\n        <- rtn as String?\n        case 1\n          rtn: \"CRITICAL\"\n        case 2\n          rtn: \"HIGH\"\n        case 3\n          rtn: \"MEDIUM\"\n        default\n          rtn: \"LOW\"\n      stdout.println(`Priority ${priority}: ${urgencyLabel}`)","migrationContext":"Java: switch expressions since Java 14 with arrow syntax and yield keyword. Rust: match is always an expression (returns a value). Kotlin: when is always an expression when exhaustive. Python: no switch expression (use dict mapping or if/else). Go: no switch expression form. C/C++: no switch expression (ternary for simple cases). C#: switch expressions since C# 8 with arrow syntax. JavaScript: no switch expression. Swift: no switch expression but if/switch can be used in return. EK9: switch expression with declared return variable, no return keyword, value returned implicitly through the variable, works with all switch features.","keywords":["assign","branch","compute","condition","control","error","expression","flow","given","guard","ok","result","return","switch","ternary","value"],"primaryTopics":["switch expression","switch return value"],"typicalErrors":[{"error":"E01072","correct":"dayName <- switch dayNumber\n        <- rtn as String?\n        case 1\n          rtn: \"Monday\"","incorrect":"switch dayNumber\n        case 1\n          return \"Monday\"","explanation":"EK9 has no return statement. Switch expressions use a declared return variable (rtn) that is assigned in each case. The final value is returned implicitly. See ek9 -h E01072 for details."},{"error":"E01070","correct":"case 1\n          rtn: \"Monday\"\n        case 2\n          rtn: \"Tuesday\"","incorrect":"case 1\n          rtn: \"Monday\"\n          break\n        case 2\n          rtn: \"Tuesday\"\n          break","explanation":"EK9 has no break statement. Each switch case is self-contained with no fallthrough, making break unnecessary and nonexistent in the grammar. See ek9 -h E01070 for details."}],"companions":[]}
{"id":69,"category":"Control Flow","question":"How do I match multiple values in one case?","url":"https://ek9.io/qa/QA0069.html","alternatePhrasings":["How do I handle multiple case values in EK9?","How does EK9 replace switch fallthrough?","Can I list several values in a single case clause?"],"answer":"EK9 uses comma-separated values in a single case clause. This replaces the fallthrough pattern found in Java, C, and JavaScript.\n\nBASIC MULTI-VALUE CASE\nList multiple values separated by commas:\n  switch day\n    case \"Monday\", \"Tuesday\", \"Wednesday\", \"Thursday\", \"Friday\"\n      dayType: \"weekday\"\n    case \"Saturday\", \"Sunday\"\n      dayType: \"weekend\"\n    default\n      dayType: \"invalid\"\nIf the value matches ANY of the listed values, the case body executes.\n\nINTEGER MULTI-VALUE\nGroup numeric codes:\n  switch errorCode\n    case 400, 401, 403, 404\n      category: \"client error\"\n    case 500, 502, 503\n      category: \"server error\"\n    default\n      category: \"other\"\n\nMIXING LITERALS AND FUNCTION CALLS\nCase values can include function calls alongside literals:\n  switch temperature\n    case currentTemperature(\"GB\"), 21, 22, 23, 24\n      comfort: \"Perfect\"\nThe function call is evaluated and compared just like the literals.\n\nWHY NO FALLTHROUGH\nOther languages use fallthrough for multi-value matching:\n  // Java fallthrough pattern (error-prone)\n  case 1:\n  case 2:\n  case 3:\n    result = \"low\";\n    break;  // forget this and bugs happen\nEK9 eliminates this entirely. CERT ranks switch fallthrough as the #7 most dangerous coding error. Multiple values per case handles the same use case safely.\n\nCOMPLEXITY\nMultiple values per case do NOT increase code complexity. 'case 25, 26, 27' is one code path, not three. The complexity is the same as a single-value case.\n\nSee Q63 for basic switch. See Q68 for switch as expression. See Q144 for why EK9 has no break/fallthrough.","ek9Example":"defines module qa.flow.switch.multivalue\n\n  defines program\n\n    MultiCaseDemo()\n      stdout <- Stdout()\n\n      // === WEEKDAY/WEEKEND GROUPING ===\n\n      days <- [\"Monday\", \"Saturday\", \"Wednesday\", \"Sunday\", \"Friday\"]\n      for day in days\n        dayType <- String()\n        switch day\n          case \"Monday\", \"Tuesday\", \"Wednesday\", \"Thursday\", \"Friday\"\n            dayType: \"weekday\"\n          case \"Saturday\", \"Sunday\"\n            dayType: \"weekend\"\n          default\n            dayType: \"invalid\"\n        stdout.println(`${day} -> ${dayType}`)\n\n      // === NUMERIC GROUPING ===\n\n      codes <- [200, 301, 404, 500]\n      for code in codes\n        category <- String()\n        switch code\n          case 200, 201, 204\n            category: \"success\"\n          case 301, 302, 304\n            category: \"redirect\"\n          case 400, 401, 403, 404\n            category: \"client error\"\n          case 500, 502, 503\n            category: \"server error\"\n          default\n            category: \"other\"\n        stdout.println(`HTTP ${code} -> ${category}`)\n\n      // === MULTI-VALUE IN EXPRESSION FORM ===\n\n      season <- 7\n      seasonName <- switch season\n        <- rtn as String?\n        case 3, 4, 5\n          rtn: \"Spring\"\n        case 6, 7, 8\n          rtn: \"Summer\"\n        case 9, 10, 11\n          rtn: \"Autumn\"\n        case 12, 1, 2\n          rtn: \"Winter\"\n        default\n          rtn: \"invalid month\"\n      stdout.println(`Month ${season} -> ${seasonName}`)","migrationContext":"Java: fallthrough by default, must add break to prevent it, switch expressions use arrow syntax since Java 14. Python: case X | Y in match/case since 3.10, uses pipe operator. Rust: pattern1 | pattern2 in match arms, uses pipe operator. Go: case X, Y with comma-separated values and no fallthrough by default. C/C++: fallthrough by default, major bug source, CERT #7 most dangerous error. Kotlin: when with comma-separated values, no fallthrough. C#: no fallthrough, multiple labels with goto case (complex). JavaScript: fallthrough by default like Java. Swift: case X, Y with comma-separated values, no fallthrough. EK9: case X, Y with comma-separated values, no fallthrough exists, no break keyword.","keywords":["branch","break","case","comma","condition","control","fallthrough","flow","group","match","migrate","multiple","several","values"],"primaryTopics":[],"typicalErrors":[{"error":"E07320","correct":"default\n            dayType: \"invalid\"","incorrect":"//no default needed","explanation":"EK9 requires a 'default' clause in switch statements to ensure all cases are handled. Omitting it triggers E07320. This prevents bugs where unexpected values silently fall through. See ek9 -h E07320 for details."},{"error":"E01070","correct":"case 200, 201, 204\n            category: \"success\"","incorrect":"case 200, 201, 204\n            category: \"success\"\n            break","explanation":"EK9 has no break statement — it does not exist in the language. Each case clause is self-contained with no fallthrough. The break keyword is deliberately excluded. See ek9 -h E01070 for details."}],"companions":[]}
{"id":70,"category":"Control Flow","question":"How do I use comparison operators in switch cases?","url":"https://ek9.io/qa/QA0070.html","alternatePhrasings":["Can I use less than or greater than in a case clause?","How do I match ranges in a switch statement?","How do comparison operators work in EK9 switch cases?"],"answer":"EK9 switch cases can use comparison operators. Instead of matching exact values, you can write 'case < 5', 'case > 100', 'case >= threshold' and similar. The operator is applied to the switch control variable.\n\nBASIC COMPARISON CASES\nUse less-than and greater-than in cases:\n  switch temperature\n    case < 0\n      label: \"freezing\"\n    case < 15\n      label: \"cold\"\n    case < 25\n      label: \"comfortable\"\n    default\n      label: \"hot\"\nOrder matters: the first matching case wins, just like if/else-if.\n\nSUPPORTED OPERATORS\nAll comparison operators work in cases:\n  case < 5       less than\n  case > 100     greater than\n  case <= 10     less than or equal\n  case >= 90     greater than or equal\n  case == 42     explicit equality\n  case <> 0      not equal\n\nEXPRESSIONS IN COMPARISONS\nThe right side of the operator can be any expression, including arithmetic and function calls:\n  multiplier <- 5\n  switch conditionValue\n    case > 10 * multiplier\n      label: \"Very High\"\n    case < 12\n      label: \"Moderate\"\n    default\n      label: \"Normal\"\n\nMIXING WITH LITERALS\nYou can mix comparison cases with literal value cases in the same switch:\n  switch score\n    case < 0\n      label: \"invalid\"\n    case 0\n      label: \"zero\"\n    case > 100\n      label: \"over maximum\"\n    default\n      label: \"valid\"\n\nORDER MATTERS\nCases are evaluated top to bottom. First match wins. Place more specific cases before general ones to avoid shadowing:\n  switch temperature\n    case < 0        checked first\n    case < 15       checked second (only if not less than 0)\n    default         everything else\n\nSwitch with comparisons works in expression form too (see Q68). Multiple values per case can be mixed with comparisons (see Q69).\n\nSee Q63 for basic switch. See Q68 for switch as expression. See Q71 for pattern matching in switch.","ek9Example":"defines module qa.flow.switch.operators\n\n  defines function\n\n    supplyMultiplier()\n      <- rtn <- 10\n\n    supplyCheckValue()\n      <- rtn <- 55\n\n  defines program\n\n    SwitchComparisonDemo()\n      stdout <- Stdout()\n\n      // === TEMPERATURE CLASSIFICATION ===\n\n      temperatures <- [35, 15, -5, 22, 0]\n      for temperature in temperatures\n        classification <- String()\n        switch temperature\n          case < 0\n            classification: \"freezing\"\n          case < 10\n            classification: \"cold\"\n          case < 20\n            classification: \"cool\"\n          case < 30\n            classification: \"warm\"\n          default\n            classification: \"hot\"\n        stdout.println(`${temperature}C -> ${classification}`)\n\n      // === GRADE CLASSIFICATION WITH EXPRESSION ===\n\n      scores <- [95, 82, 71, 55, 40]\n      for score in scores\n        grade <- switch score\n          <- rtn as String?\n          case >= 90\n            rtn: \"A\"\n          case >= 80\n            rtn: \"B\"\n          case >= 70\n            rtn: \"C\"\n          case >= 60\n            rtn: \"D\"\n          default\n            rtn: \"F\"\n        stdout.println(`Score ${score} -> Grade ${grade}`)\n\n      // === MIXING COMPARISONS WITH LITERALS ===\n\n      testValues <- [-1, 0, 50, 100, 150]\n      for testValue in testValues\n        category <- String()\n        switch testValue\n          case < 0\n            category: \"negative\"\n          case 0\n            category: \"zero\"\n          case 100\n            category: \"perfect\"\n          case > 100\n            category: \"over limit\"\n          default\n            category: \"normal\"\n        stdout.println(`${testValue} -> ${category}`)\n\n      // === EXPRESSIONS IN COMPARISONS ===\n\n      multiplier <- supplyMultiplier()\n      checkValue <- supplyCheckValue()\n      assessment <- switch checkValue\n        <- rtn as String?\n        case > 10 * multiplier\n          rtn: \"exceeds scaled limit\"\n        case > multiplier\n          rtn: \"above base\"\n        default\n          rtn: \"within range\"\n      stdout.println(`${checkValue} -> ${assessment}`)","migrationContext":"Java: no comparison operators in case labels, must use if/else-if chains. C#: relational patterns since C# 9 (case < 12 in switch expressions), closest to EK9. Rust: match guards with if (match x { n if n < 5 => ... }), separate from pattern. Python: no comparison in match/case, use if/elif. Go: no comparison in case, use if/else. Kotlin: when supports arbitrary boolean expressions (in 1..10, is Type), very flexible. C/C++: no comparison in case, constants only. JavaScript: no comparison in case labels. Swift: where clause for conditions (case let x where x < 5). EK9: comparison operators directly in case clause, mixed with literals and function calls, no separate guard syntax needed.","keywords":["branch","case","comparison","condition","control","flow","greater","inequality","less","operator","range","switch","threshold"],"primaryTopics":[],"typicalErrors":[{"error":"E01070","correct":"switch temperature\n          case < 0\n            classification: \"freezing\"","incorrect":"switch temperature\n          case < 0\n            classification: \"freezing\"\n            break","explanation":"EK9 has no break statement. Switch cases with comparison operators are self-contained like all other case clauses. No fallthrough exists. See ek9 -h E01070 for details."}],"companions":[]}
{"id":71,"category":"Control Flow","question":"How do pattern matching and regex work in switch?","url":"https://ek9.io/qa/QA0071.html","alternatePhrasings":["Can I use regex in a switch case in EK9?","How do I match patterns in a switch statement?","How does case matches work in EK9?"],"answer":"EK9 switch cases support pattern matching with the 'matches' and 'contains' operators. These work with String values.\n\nREGEX MATCHING\nUse 'case matches' with a regex literal:\n  switch userInput\n    case matches /^[A-Z]{2}[0-9]{4}$/\n      category: \"product code\"\n    case matches /^[0-9]+$/\n      category: \"numeric\"\n    default\n      category: \"text\"\nThe regex literal uses /pattern/ syntax. The switch value is tested against the pattern.\n\nSUBSTRING MATCHING\nUse 'case contains' to check for substrings:\n  switch logLine\n    case contains \"ERROR\"\n      severity: \"high\"\n    case contains \"WARN\"\n      severity: \"medium\"\n    default\n      severity: \"low\"\n\nMIXING PATTERN TYPES\nYou can mix regex, contains, comparison operators, and literal values in the same switch:\n  switch conditionVariable\n    case 'D'\n      label: \"exact match\"\n    case matches /[nN]ame/\n      label: \"regex match\"\n    case > \"Gandalf\"\n      label: \"comparison match\"\n    default\n      label: \"default\"\nThis is a powerful combination unique to EK9.\n\nEXPRESSION FORM\nPattern matching works in switch expressions too:\n  result <- switch text\n    <- rtn as String?\n    case matches /pattern/\n      rtn: \"matched\"\n    default\n      rtn: \"no match\"\n\nORDER MATTERS\nAs with all switch cases, order matters. Place more specific patterns before general ones to avoid shadowing.\n\nSee Q33 for regex type details. See Q37 for String type details. See Q63 for basic switch. See Q70 for comparison operators in cases.","ek9Example":"defines module qa.flow.switch.pattern\n\n  defines program\n\n    SwitchPatternDemo()\n      stdout <- Stdout()\n\n      // === REGEX MATCHING ===\n\n      inputs <- [\"AB1234\", \"hello\", \"42\", \"XY9999\"]\n      for userInput in inputs\n        category <- switch userInput\n          <- rtn as String?\n          case matches /^[A-Z]{2}[0-9]{4}$/\n            rtn: \"product code\"\n          case matches /^[0-9]+$/\n            rtn: \"numeric\"\n          default\n            rtn: \"text\"\n        stdout.println(`\"${userInput}\" -> ${category}`)\n\n      // === CONTAINS MATCHING ===\n\n      logLines <- [\"2024 ERROR disk full\", \"2024 WARN low memory\", \"2024 INFO started\"]\n      for logLine in logLines\n        severity <- switch logLine\n          <- rtn as String?\n          case contains \"ERROR\"\n            rtn: \"HIGH\"\n          case contains \"WARN\"\n            rtn: \"MEDIUM\"\n          default\n            rtn: \"LOW\"\n        stdout.println(`${severity}: ${logLine}`)\n\n      // === MIXED PATTERNS ===\n\n      names <- [\"Dave\", \"Name\", \"Zara\", \"Alice\"]\n      for nameEntry in names\n        assessment <- switch nameEntry\n          <- rtn as String?\n          case matches /[nN]ame/\n            rtn: \"contains 'name'\"\n          case > \"M\"\n            rtn: \"second half of alphabet\"\n          case < \"D\"\n            rtn: \"early alphabet\"\n          default\n            rtn: \"mid alphabet\"\n        stdout.println(`${nameEntry} -> ${assessment}`)","migrationContext":"Rust: match with pattern matching and destructuring, no inline regex, separate regex crate. Python: match/case since 3.10 with structural patterns, no regex in case clauses. Kotlin: when with arbitrary expressions, regex via matches() in guards. Java: no pattern matching in switch cases (instanceof patterns only since Java 21). Go: no pattern matching in switch. C/C++: no pattern matching in switch. JavaScript: no pattern matching in switch. Swift: pattern matching with case let and where clauses, no inline regex. Ruby: case/when with regex via === operator. EK9: case matches /regex/ and case contains \"text\" directly in case clauses, mixed with comparison operators and literals.","keywords":["branch","case","condition","contains","control","flow","matches","pattern","regex","regular","string","substring","switch"],"primaryTopics":[],"typicalErrors":[{"error":"E01070","correct":"case matches /^[A-Z]{2}[0-9]{4}$/\n            rtn: \"product code\"","incorrect":"case matches /^[A-Z]{2}[0-9]{4}$/\n            rtn: \"product code\"\n            break","explanation":"EK9 has no break statement. Switch cases with pattern matching are self-contained like all other cases. No fallthrough exists in the language. See ek9 -h E01070 for details."},{"error":"E01072","correct":"case contains \"ERROR\"\n            rtn: \"HIGH\"\n          case contains \"WARN\"\n            rtn: \"MEDIUM\"","incorrect":"case contains \"ERROR\"\n            return \"HIGH\"","explanation":"EK9 has no return statement. Switch expressions use a declared return variable that is assigned in each case branch. See ek9 -h E01072 for details."}],"companions":[]}
{"id":72,"category":"Control Flow","question":"What are given/when — alternative switch keywords?","url":"https://ek9.io/qa/QA0072.html","alternatePhrasings":["Can I use given instead of switch in EK9?","What is the difference between switch/case and given/when?","Are given and when just synonyms for switch and case?"],"answer":"Yes. EK9 provides 'given' as a synonym for 'switch', and 'when' as a synonym for 'case'. They are semantically identical — the choice is purely stylistic.\n\nBASIC GIVEN/WHEN\nUse given/when instead of switch/case:\n  given direction\n    when \"north\"\n      y: y + 1\n    when \"south\"\n      y: y - 1\n    default\n      stdout.println(\"Unhandled\")\nThis is exactly equivalent to using switch/case.\n\nALL FOUR COMBINATIONS\nYou can mix the keywords freely:\n  switch x + case:     switch value case 1 ...\n  switch x + when:     switch value when 1 ...\n  given x + case:      given value case 1 ...\n  given x + when:      given value when 1 ...\nAll four are valid and behave identically.\n\nEXPRESSION FORM\nGiven/when works in expression form:\n  outcome <- given isReady\n    <- rtn as String?\n    when true\n      rtn: \"proceed\"\n    default\n      rtn: \"wait\"\n\nWITH ALL FEATURES\nAll switch features work with given/when:\n  given temperature\n    when < 0\n      label: \"freezing\"\n    when 20, 21, 22\n      label: \"comfortable\"\n    default\n      label: \"other\"\nComparison operators, multiple values, pattern matching, guards, and exhaustive enum matching all work identically.\n\nWHEN IN IF STATEMENTS\nThe 'when' keyword is also a synonym for 'if':\n  when temperature < 0\n    stdout.println(\"freezing\")\nSee Q61 for if/when usage.\n\nSTYLE PREFERENCE\nSome developers prefer given/when because it reads more naturally for certain types of logic, especially with Boolean conditions. Others prefer switch/case for familiarity. EK9 supports both to accommodate different preferences.\n\nSee Q63 for basic switch/case. See Q68 for switch/given as expression. See Q61 for if/when statements.","ek9Example":"defines module qa.flow.given.style\n\n  defines function\n\n    supplyStatusCode() as pure\n      -> code as Integer\n      <- rtn as Integer: code\n\n    supplyReady() as pure\n      -> ready as Boolean\n      <- rtn as Boolean: ready\n\n    supplyPriority() as pure\n      -> level as Integer\n      <- rtn as Integer: level\n\n    supplyTemperature() as pure\n      -> degrees as Integer\n      <- rtn as Integer: degrees\n\n    supplyDay() as pure\n      -> day as String\n      <- rtn as String: day\n\n  defines program\n\n    GivenWhenDemo()\n      stdout <- Stdout()\n\n      // === BASIC GIVEN/WHEN ===\n\n      statusCode <- supplyStatusCode(200)\n      statusMessage <- String()\n      given statusCode\n        when 200\n          statusMessage: \"OK\"\n        when 404\n          statusMessage: \"Not Found\"\n        when 500\n          statusMessage: \"Server Error\"\n        default\n          statusMessage: \"Unknown\"\n      stdout.println(`Status ${statusCode}: ${statusMessage}`)\n\n      // === GIVEN/WHEN AS EXPRESSION ===\n\n      isReady <- supplyReady(true)\n      outcome <- given isReady\n        <- rtn as String?\n        when true\n          rtn: \"Proceed\"\n        default\n          rtn: \"Wait\"\n      stdout.println(`Ready: ${outcome}`)\n\n      // === SWITCH WITH WHEN (MIXED) ===\n\n      priority <- supplyPriority(1)\n      urgency <- String()\n      switch priority\n        when 1\n          urgency: \"Critical\"\n        when 2\n          urgency: \"High\"\n        when 3\n          urgency: \"Medium\"\n        default\n          urgency: \"Low\"\n      stdout.println(`Priority ${priority}: ${urgency}`)\n\n      // === GIVEN WITH COMPARISON OPERATORS ===\n\n      temperature <- supplyTemperature(18)\n      comfort <- String()\n      given temperature\n        when < 10\n          comfort: \"cold\"\n        when < 20\n          comfort: \"cool\"\n        when < 30\n          comfort: \"warm\"\n        default\n          comfort: \"hot\"\n      stdout.println(`${temperature}C is ${comfort}`)\n\n      // === GIVEN/WHEN WITH MULTIPLE VALUES ===\n\n      dayOfWeek <- supplyDay(\"Wednesday\")\n      scheduleType <- String()\n      given dayOfWeek\n        when \"Monday\", \"Wednesday\", \"Friday\"\n          scheduleType: \"gym day\"\n        when \"Tuesday\", \"Thursday\"\n          scheduleType: \"rest day\"\n        when \"Saturday\", \"Sunday\"\n          scheduleType: \"weekend\"\n        default\n          scheduleType: \"unknown\"\n      stdout.println(`${dayOfWeek} is ${scheduleType}`)","migrationContext":"Kotlin: uses 'when' as its switch equivalent (not 'switch' keyword), very similar to EK9's given/when. Ruby: uses 'case/when' syntax, similar to EK9's alternative. Python: uses 'match/case' since 3.10. Rust: uses 'match' keyword. Go: uses 'switch/case'. Java: uses 'switch/case'. C/C++: uses 'switch/case'. JavaScript: uses 'switch/case'. Swift: uses 'switch/case'. EK9: provides both switch/case and given/when as interchangeable synonyms, plus 'when' as synonym for 'if'.","keywords":["alternative","branch","case","condition","control","flow","given","keyword","preference","style","switch","synonym","when"],"primaryTopics":[],"typicalErrors":[{"error":"E01070","correct":"given statusCode\n        when 200\n          statusMessage: \"OK\"","incorrect":"given statusCode\n        when 200\n          statusMessage: \"OK\"\n          break","explanation":"EK9 has no break statement. The given/when construct (synonym for switch/case) has no fallthrough, so break is unnecessary and nonexistent. See ek9 -h E01070 for details."},{"error":"E01072","correct":"outcome <- given isReady\n        <- rtn as String?\n        when true\n          rtn: \"Proceed\"\n        default\n          rtn: \"Wait\"","incorrect":"given isReady\n        when true\n          return \"Proceed\"\n        default\n          return \"Wait\"","explanation":"EK9 has no return statement. Given/when expressions use a declared return variable that is assigned in each branch. See ek9 -h E01072 for details."}],"companions":[]}
{"id":73,"category":"Control Flow","question":"How does exhaustive enum switch work?","url":"https://ek9.io/qa/QA0073.html","alternatePhrasings":["Does EK9 check that all enum values are handled in a switch?","How do I switch over an enumeration in EK9?","What happens if I miss an enum value in a switch?"],"answer":"When you switch over an enumeration and use direct constant references in cases, EK9's compiler verifies that ALL enumeration values are covered. Missing a value is a compile-time error.\n\nEXHAUSTIVE ENUM SWITCH\nAll enum values must appear in cases:\n  switch heading\n    case Direction.North\n      label: \"going north\"\n    case Direction.South\n      label: \"going south\"\n    case Direction.East\n      label: \"going east\"\n    case Direction.West\n      label: \"going west\"\n    default\n      label: \"direction not set\"\nThe compiler checks that North, South, East, and West are ALL present. Missing one is a compile error.\n\nDEFAULT IS REQUIRED\nEven with all enum values listed, a 'default' block is required. This handles the case where the enum variable is unset (EK9's tri-state semantics mean a variable can be present but have no value).\n\nCOMPILE-TIME SAFETY\nIf you later add a new value to the enum (say Direction.NorthWest), the compiler will flag every exhaustive switch that is now incomplete. This forces you to handle the new value everywhere, preventing silent bugs.\n\nNON-EXHAUSTIVE OPT-OUT\nUse the equality operator '==' to disable exhaustive checking:\n  switch heading\n    case == Direction.North\n      label: \"going north\"\n    default\n      label: \"not north\"\nWith '== Direction.North' instead of 'Direction.North', the compiler treats this as a comparison, not an exhaustive match. You can handle only the values you care about.\n\nMULTIPLE ENUM VALUES PER CASE\nGroup enum values with commas:\n  switch heading\n    case Direction.North, Direction.South\n      axis: \"vertical\"\n    case Direction.East, Direction.West\n      axis: \"horizontal\"\n    default\n      axis: \"none\"\nThis is exhaustive: all four values are covered across the two cases.\n\nEXPRESSION FORM\nExhaustive enum switch works in expression form:\n  label <- switch heading\n    <- rtn as String?\n    case Direction.North\n      rtn: \"N\"\n    ...\n    default\n      rtn: \"?\"\n\nSee Q63 for basic switch. See Q68 for switch expression form. See Q69 for multiple values per case. See Q99 for creating enumerations. See Q100 for how EK9 enums differ from Java enums. See Q226 for enum bug prevention.","ek9Example":"defines module qa.flow.switch.exhaustive\n\n  defines type\n\n    Direction\n      North,\n      South,\n      East,\n      West\n\n  defines function\n\n    describeDirection() as pure\n      -> heading as Direction\n      <- description <- String()\n\n      //Exhaustive: all four values must be present\n      switch heading\n        case Direction.North\n          description: \"heading north\"\n        case Direction.South\n          description: \"heading south\"\n        case Direction.East\n          description: \"heading east\"\n        case Direction.West\n          description: \"heading west\"\n        default\n          description: \"direction not set\"\n\n    describeAxis() as pure\n      -> heading as Direction\n      <- axis <- String()\n\n      //Exhaustive with grouped values\n      switch heading\n        case Direction.North, Direction.South\n          axis: \"vertical\"\n        case Direction.East, Direction.West\n          axis: \"horizontal\"\n        default\n          axis: \"none\"\n\n    isNorth() as pure\n      -> heading as Direction\n      <- northward <- String()\n\n      //Non-exhaustive: uses == operator to opt out\n      switch heading\n        case == Direction.North\n          northward: \"yes, heading north\"\n        default\n          northward: \"not heading north\"\n\n  defines program\n\n    EnumSwitchDemo()\n      stdout <- Stdout()\n\n      // === EXHAUSTIVE SWITCH ===\n\n      directions <- [Direction.North, Direction.South, Direction.East, Direction.West]\n      for heading in directions\n        stdout.println(describeDirection(heading))\n\n      // === GROUPED ENUM VALUES ===\n\n      for heading in directions\n        stdout.println(`${describeDirection(heading)} is on ${describeAxis(heading)} axis`)\n\n      // === NON-EXHAUSTIVE WITH == ===\n\n      for heading in directions\n        stdout.println(isNorth(heading))\n\n      // === EXPRESSION FORM ===\n\n      testHeading <- Direction.East\n      symbol <- switch testHeading\n        <- rtn as String?\n        case Direction.North\n          rtn: \"N\"\n        case Direction.South\n          rtn: \"S\"\n        case Direction.East\n          rtn: \"E\"\n        case Direction.West\n          rtn: \"W\"\n        default\n          rtn: \"?\"\n      stdout.println(`Direction symbol: ${symbol}`)","migrationContext":"Rust: match on enum is exhaustive, compiler error if arm missing, _ for wildcard. Kotlin: when on sealed class or enum is exhaustive when used as expression, else required. Java: switch on enum since Java 5, exhaustive checking only with switch expressions since Java 21 with sealed types. Python: match/case has no exhaustive checking for enums. Go: no enum type (iota constants), no exhaustive checking. C/C++: switch on enum has optional compiler warnings for missing cases (-Wswitch), not enforced. C#: switch on enum not exhaustive by default, Roslyn analyzers can check. Swift: switch on enum is exhaustive, compiler error if case missing, @unknown default for future values. EK9: switch on enum is exhaustive when using direct constant references, use == operator to opt out, default always required for unset handling.","keywords":["branch","check","compiler","complete","condition","control","cover","enum","enumeration","exhaustive","flow","migrate","missing","switch"],"primaryTopics":[],"typicalErrors":[{"error":"E01070","correct":"switch heading\n        case Direction.North\n          description: \"heading north\"","incorrect":"switch heading\n        case Direction.North\n          description: \"heading north\"\n          break","explanation":"EK9 has no break statement. Exhaustive enum switch cases are self-contained with no fallthrough. See ek9 -h E01070 for details."},{"error":"E01072","correct":"describeDirection() as pure\n      -> heading as Direction\n      <- description <- String()","incorrect":"describeDirection() as pure\n      -> heading as Direction\n      switch heading\n        case Direction.North\n          return \"heading north\"","explanation":"EK9 has no return statement. Functions use declared return variables. The compiler verifies all paths initialise the return variable. See ek9 -h E01072 for details."}],"companions":[]}
{"id":74,"category":"Control Flow","question":"How do guard variables work in if statements?","url":"https://ek9.io/qa/QA0074.html","alternatePhrasings":["How do I combine variable declaration with an if condition?","What is the if guard pattern in EK9?","How does EK9 handle null-safe if checks?","How do I use the declaration operator in an if statement?"],"answer":"Guard variables let you combine variable creation with an isSet check in a single if statement. The guard ensures the if body only executes when the value is meaningful.\n\nBASIC GUARD PATTERN\nUse the declaration operator (<-) to create a guard variable:\n  if value <- getResult()\n    stdout.println(value)\nThe variable 'value' is created and checked. If getResult() returns an unset value, the if body is skipped entirely. No exception, no crash.\n\nGUARD WITH ADDITIONAL CONDITION\nUse 'with' or 'then' to add a second condition after the guard:\n  if value <- getResult() with value > 10\n    stdout.println(value)\nBoth must be true: the guard must be SET and the extra condition must pass. Short-circuit applies: if the guard is unset, the condition is never evaluated.\n\nIF/ELSE WITH GUARD\nThe else branch executes when the guard is unset:\n  if value <- getResult()\n    process(value)\n  else\n    handleMissing()\n\nTHREE GUARD OPERATORS\nEK9 supports three operators in if guards, each with different semantics:\n  1. Declaration (<-): Creates NEW variable, checks isSet of result\n     if value <- getResult()\n  2. Assignment (:=): Assigns to EXISTING variable, NO isSet check (blind)\n     if existing := getResult() with existing > 0\n  3. Guarded assignment (?=): Assigns to EXISTING variable, checks isSet of RHS\n     if existing ?= getResult()\nThe declaration guard (<-) is by far the most common and recommended form.\n\nSCOPING\nGuard variables created with <- are scoped to the if/else block. They do not leak into the surrounding scope, preventing accidental reuse of one-time values.\n\nWHAT 'SET' MEANS\nThe guard calls the type's isSet (?) operator. Each type defines what 'set' means: Integer is set when it has a numeric value, String when it has text content, Optional when it contains a value, collections when they have been properly initialized.\n\nSee Q29 for unset variables. See Q61 for basic if/else without guards. See Q75 for guards in switch. See Q76 for guards in for loops. See Q77 for guards in while loops. See Q78 for guards in try blocks. See Q166 for consistent safe pattern. See Q269 for input validation with guards. See Q272 for defense in depth with guards. See Q285 for multiple precondition validation with guards.","ek9Example":"defines module qa.flow.guard.ifstatement\n\n  defines record\n\n    Config\n      host <- String()\n      port <- Integer()\n\n      default operator ?\n\n  defines function\n\n    getConfig()\n      <- rtn <- Config()\n\n    getActiveConfig()\n      <- rtn <- Config()\n      rtn.host: \"localhost\"\n      rtn.port: 8080\n\n  defines program\n\n    GuardIfDemo()\n      stdout <- Stdout()\n\n      // === BASIC GUARD: if value <- expr() ===\n\n      if config <- getActiveConfig()\n        stdout.println(\"Got config: \" + config.host)\n      else\n        stdout.println(\"No config available\")\n\n      // === GUARD WITH ADDITIONAL CONDITION ===\n\n      if config <- getActiveConfig() with config.port > 0\n        stdout.println(\"Config port: \" + $config.port)\n\n      // === GUARD WITH UNSET VALUE ===\n\n      if config <- getConfig()\n        stdout.println(\"This should not print\")\n      else\n        stdout.println(\"Config was unset, handled safely\")\n\n      // === ASSIGNMENT GUARD (no isSet check) ===\n\n      existing <- Config()\n      if existing := getActiveConfig() with existing.port > 0\n        stdout.println(\"Assigned: \" + existing.host)\n\n      // === WHEN KEYWORD (alternative to if) ===\n\n      when config <- getActiveConfig()\n        stdout.println(\"When guard works too: \" + config.host)","migrationContext":"Java: No guard syntax. Must write: 'var v = getValue(); if (v != null) { use(v); }' as two separate steps. Variable leaks into outer scope. Optional requires: 'getValue().ifPresent(v -> use(v))' which forces a lambda. Python: No guard syntax. Walrus operator (:=) in 3.8+ comes close: 'if (v := getValue()) is not None:' but only handles None, not general isSet semantics. Rust: 'if let Some(v) = get_value()' for Option destructuring. Close to EK9 guards but limited to pattern matching. Go: 'if v := getValue(); v != nil' allows init statement in if but uses nil checks, no isSet concept. Kotlin: 'val v = getValue(); if (v != null)' with smart cast. No single-expression guard. Swift: 'if let v = getValue()' for optional binding, closest to EK9 but limited to Optional type. EK9: 'if value <- getResult()' combines declaration, assignment, and isSet check in one expression. Works with any type that has the ? operator, not just Optional. Three operator variants for different semantics.","keywords":["branch","check","condition","control","declaration","flow","guard","if","isset","null","null-safe","operator","safe","scope","unset"],"primaryTopics":["guard","guard variable","guard expression"],"typicalErrors":[{"error":"E01072","correct":"if config <- getActiveConfig()\n        stdout.println(\"Got config: \" + config.host)\n      else\n        stdout.println(\"No config available\")","incorrect":"config <- getActiveConfig()\n      if not config?\n        return\n      stdout.println(\"Got config: \" + config.host)","explanation":"EK9 has no return statement. Guard variables in if statements combine declaration and isSet checking in one expression, replacing the early-return-if-null pattern. See ek9 -h E01072 for details."}],"companions":[]}
{"id":75,"category":"Control Flow","question":"How do guard variables work in switch statements?","url":"https://ek9.io/qa/QA0075.html","alternatePhrasings":["Can I guard a switch with a variable declaration?","How does switch with guard work in EK9?","What happens if a switch guard is unset?"],"answer":"Guard variables in switch combine variable creation with an isSet check before the switch body executes. If the guard is unset, the entire switch (including default) is skipped.\n\nBASIC SWITCH GUARD\nUse the declaration operator (<-) with 'then' or 'with' to link the guard to the switch control:\n  switch value <- getResult() then value\n    case 1\n      stdout.println(\"One\")\n    case 2\n      stdout.println(\"Two\")\n    default\n      stdout.println(\"Other\")\nIf getResult() is unset, nothing executes. No exception.\n\nSWITCH EXPRESSION WITH GUARD\nSwitch expressions return a value. The guard protects the entire expression:\n  label <- switch priority <- getPriority() then priority\n    <- rtn as String: \"Unknown\"\n    case 1\n      rtn: \"Low\"\n    case 2\n      rtn: \"Medium\"\n    case 3\n      rtn: \"High\"\n    default\n      rtn: \"Other\"\nIf the guard is unset, the expression evaluates to the default return value (\"Unknown\").\n\nKEYWORD INTERCHANGEABILITY\n'with' and 'then' are synonyms. Use whichever reads better:\n  switch value <- getResult() with value\n  switch value <- getResult() then value\nBoth are identical in behavior.\n\nGIVEN/WHEN ALTERNATIVE\nEK9 allows 'given' as a synonym for 'switch':\n  given value <- getResult() then value\n    when 1\n      stdout.println(\"One\")\nSee Q72 for more on given/when.\n\nSee Q63 for basic switch without guards. See Q68 for switch as expression. See Q74 for guards in if statements. See Q76 for guards in for loops. See Q243 for coalescing operators (??, ?:, <?, >?) that provide another approach to handling unset values.","ek9Example":"defines module qa.flow.guard.switchstatement\n\n  defines function\n\n    getPriority()\n      <- rtn <- Integer()\n      rtn: 2\n\n    getUnsetPriority()\n      <- rtn <- Integer()\n\n  defines program\n\n    GuardSwitchDemo()\n      stdout <- Stdout()\n\n      // === BASIC SWITCH GUARD ===\n\n      switch priority <- getPriority() then priority\n        case 1\n          stdout.println(\"Low priority\")\n        case 2\n          stdout.println(\"Medium priority\")\n        case 3\n          stdout.println(\"High priority\")\n        default\n          stdout.println(\"Unknown priority\")\n\n      // === SWITCH EXPRESSION WITH GUARD ===\n\n      label <- switch priority <- getPriority() then priority\n        <- rtn as String: \"Unknown\"\n        case 1\n          rtn: \"Low\"\n        case 2\n          rtn: \"Medium\"\n        case 3\n          rtn: \"High\"\n        default\n          rtn: \"Other\"\n      stdout.println(\"Priority label: \" + label)\n\n      // === GUARD WITH UNSET VALUE (entire switch skipped) ===\n\n      switch priority <- getUnsetPriority() then priority\n        case 1\n          stdout.println(\"This should not print\")\n        default\n          stdout.println(\"Default should not print either\")\n\n      stdout.println(\"After guarded switch with unset value\")","migrationContext":"Java: Java 21+ pattern matching in switch cannot combine guard declarations. Must write: 'var v = getValue(); switch (v) { ... }' as two steps. Variable leaks into outer scope. Kotlin: 'when' blocks cannot combine guard declaration with control expression. Must nest: 'val v = expr; when (v) { ... }'. Swift: 'switch expr' cannot combine optional binding with switch control. Must nest: 'if let v = expr { switch v { ... } }'. Go: No guard declarations in switch. Must declare and check separately. EK9: 'switch value <- getResult() then value' combines declaration, isSet check, and switch control in one line. Guards scope the variable to the switch block.","keywords":["branch","case","condition","control","declaration","expression","flow","given","guard","isset","null-safe","safe","scope","switch","unset"],"primaryTopics":[],"typicalErrors":[{"error":"E01072","correct":"switch priority <- getPriority() then priority\n        case 1\n          stdout.println(\"Low priority\")\n        case 2\n          stdout.println(\"Medium priority\")","incorrect":"priority <- getPriority()\n      if not priority?\n        return\n      switch priority\n        case 1\n          stdout.println(\"Low priority\")\n          break","explanation":"EK9 has no return or break statements. Guard variables in switch combine declaration and isSet checking. Each case is self-contained with no fallthrough. See ek9 -h E01072 for details."},{"error":"E01072","correct":"label <- switch priority <- getPriority() then priority\n        <- rtn as String: \"Unknown\"\n        case 1\n          rtn: \"Low\"","incorrect":"switch priority <- getPriority() then priority\n        case 1\n          return \"Low\"","explanation":"EK9 has no return statement. Switch expressions use a declared return variable. The value is returned implicitly through the variable. See ek9 -h E01072 for details."}],"companions":[]}
{"id":76,"category":"Control Flow","question":"How do guard variables work in for loops?","url":"https://ek9.io/qa/QA0076.html","alternatePhrasings":["Can I guard a for loop with a variable declaration?","How does the for loop guard work in EK9?","What happens if a for loop guard is unset?"],"answer":"In EK9, guard variables in for loops combine variable creation with an isSet check before the loop begins. The guard executes ONCE as a preamble, not on each iteration.\n\nBASIC FOR-IN GUARD\nUse the declaration operator (<-) with 'with' to link the guard to the loop:\n  for config <- getConfig() with item in items\n    process(config, item)\nIf getConfig() is unset, the entire loop is skipped. No exception.\n\nFOR-RANGE GUARD\nGuards also work with range-based for loops:\n  for config <- getConfig() with i in 1 ... 10\n    stdout.println(config.host + \": iteration \" + $i)\n\nGUARD TIMING\nThe guard evaluates ONCE before iteration begins. This is different from while loop guards that re-evaluate on each iteration. The guard sets up a precondition for the entire loop.\n\nFOR EXPRESSION WITH GUARD\nFor loops can be expressions that return a value:\n  result <- for config <- getConfig() with i in 1 ... 3\n    <- rtn as String: \"no config\"\n    rtn: config.host + \" \" + $i\nIf the guard is unset, the expression evaluates to the default return value.\n\nWITHOUT A GUARD\nThe standard for-in loop needs no guard when iterating a collection:\n  for item in items\n    process(item)\nGuards add safety when a precondition must be met before iterating.\n\nSee Q64 for for-range loops. See Q65 for for-in loops. See Q74 for guards in if statements. See Q77 for guards in while loops (with re-evaluation).","ek9Example":"defines module qa.flow.guard.forloop\n\n  defines record\n\n    ServerConfig\n      host <- String()\n      port <- Integer()\n\n      default operator ?\n\n  defines function\n\n    getServerConfig()\n      <- rtn <- ServerConfig()\n      rtn.host: \"api.example.com\"\n      rtn.port: 443\n\n    getNoConfig()\n      <- rtn <- ServerConfig()\n\n  defines program\n\n    GuardForDemo()\n      stdout <- Stdout()\n\n      items <- [\"alpha\", \"beta\", \"gamma\"]\n\n      // === FOR-IN WITH GUARD ===\n\n      for config <- getServerConfig() with item in items\n        stdout.println(`${config.host} processing: ${item}`)\n\n      // === FOR-RANGE WITH GUARD ===\n\n      for config <- getServerConfig() with i in 1 ... 3\n        stdout.println(`Iteration ${i} on ${config.host}`)\n\n      // === GUARD WITH UNSET VALUE (loop skipped) ===\n\n      for config <- getNoConfig() with item in items\n        stdout.println(\"This should not print: \" + item)\n\n      stdout.println(\"After guarded for with unset value\")","migrationContext":"Java: No guard concept in for loops. Must check preconditions separately: 'var c = getConfig(); if (c != null) { for (item : items) { ... } }'. Python: No guard in for loops. Must wrap: 'config = get_config(); if config: for item in items: ...'. Rust: No guard in for loops. Must wrap: 'if let Some(c) = get_config() { for item in items { ... } }'. Go: No guard in for loops. Must check nil separately. EK9: 'for config <- getConfig() with item in items' combines precondition check and iteration in one statement. Guard evaluates once before loop begins.","keywords":["branch","condition","control","declaration","flow","for","guard","isset","iteration","loop","null-safe","preamble","precondition","range","safe"],"primaryTopics":[],"typicalErrors":[{"error":"E01072","correct":"for config <- getServerConfig() with item in items\n        stdout.println(`${config.host} processing: ${item}`)","incorrect":"config <- getServerConfig()\n      if not config?\n        return\n      for item in items\n        stdout.println(`${config.host} processing: ${item}`)","explanation":"EK9 has no return statement. Guard variables in for loops combine the precondition check and iteration in one statement, replacing the check-then-return pattern. See ek9 -h E01072 for details."},{"error":"E01070","correct":"      for config <- getServerConfig() with i in 1 ... 3\n        stdout.println(`Iteration ${i} on ${config.host}`)","incorrect":"for config <- getServerConfig() with i in 1 ... 3\n        stdout.println(`Iteration ${$i} on ${config.host}`)\n        if i == 2\n          break","explanation":"EK9 has no break statement. For loops with guards always iterate through the full range. Use stream pipelines with head if you need to stop early. See ek9 -h E01070 for details."}],"companions":[]}
{"id":77,"category":"Control Flow","question":"How do guard variables work in while loops?","url":"https://ek9.io/qa/QA0077.html","alternatePhrasings":["Can I guard a while loop with a variable declaration?","How does the while loop guard work in EK9?","What is the difference between while guards and for guards?"],"answer":"In EK9, guard variables in while loops combine variable creation with an isSet check. Unlike for loop guards that evaluate once, while loop guards are re-evaluated on EVERY iteration.\n\nBASIC WHILE GUARD\nUse the declaration operator (<-) with 'with' to link the guard to the loop condition:\n  while value <- getNextItem() with value > 0\n    process(value)\nBoth checks happen each iteration: first the guard (isSet), then the condition (> 0). If either fails, the loop terminates.\n\nGUARD RE-EVALUATION\nThis is the key difference from for loop guards. In a while loop, the guard expression is called again each time the loop condition is checked. This makes while guards perfect for consuming iterators or polling:\n  while item <- source.next() with item?\n    process(item)\n\nGUARD TERMINATES LOOP\nIf the guard becomes unset on any iteration, the loop terminates cleanly. No exception is thrown. This is a safe alternative to checking for null in a while condition.\n\nWHILE EXPRESSION WITH GUARD\nWhile loops can be expressions that return a value:\n  result <- while value <- getNext() with value > 0\n    <- rtn as String: \"none found\"\n    rtn: $value\nIf the guard is unset on the first check, the expression evaluates to the default return value.\n\nTHREE GUARD OPERATORS IN WHILE\nAll three guard operators work in while loops:\n  1. Declaration (<-): Creates variable, checks isSet each iteration\n     while value <- getNext() with condition\n  2. Assignment (:=): Assigns each iteration, NO isSet check\n     while existing := getNext() with existing > 0\n  3. Guarded assignment (?=): Assigns only if RHS is SET\n     while existing ?= getNext() with condition\n\nSee Q66 for basic while loops. See Q67 for do-while loops. See Q74 for guards in if. See Q76 for guards in for loops (one-time evaluation).","ek9Example":"defines module qa.flow.guard.whileloop\n\n  defines function\n\n    supplyCounter()\n      <- rtn <- Integer()\n      rtn: 5\n\n    supplyStart() as pure\n      -> initial as Integer\n      <- rtn as Integer: initial\n\n  defines program\n\n    GuardWhileDemo()\n      stdout <- Stdout()\n\n      // === BASIC WHILE GUARD ===\n\n      counter <- supplyStart(3)\n      while guardedCount <- supplyCounter() with counter > 0\n        stdout.println(`Counter: ${counter} value: ${guardedCount}`)\n        counter: counter - 1\n\n      // === ASSIGNMENT GUARD (no isSet check) ===\n\n      loops <- supplyStart(2)\n      existing <- Integer()\n      while existing := supplyCounter() with loops > 0\n        stdout.println(\"Assigned value: \" + $existing)\n        loops: loops - 1\n\n      stdout.println(\"While guard demo complete\")","migrationContext":"Java: No guard in while. Must write: 'T v; while ((v = getNext()) != null && v > 0) { ... }' mixing assignment and null check in condition. Error-prone. Python: No guard in while. Walrus operator helps: 'while (v := get_next()) is not None and v > 0:' but only handles None. Rust: 'while let Some(v) = get_next()' for iterator consumption. Close to EK9 but limited to pattern matching. Go: No guard in while (Go only has for). Must use: 'for v := getNext(); v != nil; v = getNext() { ... }'. EK9: 'while value <- getNext() with value > 0' combines declaration, isSet check, and loop condition. Guard re-evaluates each iteration, perfect for consuming sequences.","keywords":["branch","condition","consume","control","declaration","flow","guard","isset","iteration","loop","null-safe","poll","reevaluate","safe","terminate","while"],"primaryTopics":[],"typicalErrors":[{"error":"E07390","correct":"      while guardedCount <- supplyCounter() with counter > 0\n        stdout.println(`Counter: ${counter} value: ${guardedCount}`)\n        counter: counter - 1","incorrect":"while true\n        guardedCount <- supplyCounter()\n        if not guardedCount?\n          break\n        if counter <= 0\n          break\n        stdout.println(`Counter: ${$counter} value: ${$guardedCount}`)\n        counter: counter - 1","explanation":"EK9 has no break statement. While loop guards combine declaration, isSet checking, and the loop condition, eliminating the need for break-based exit patterns. See ek9 -h E07390 for details."}],"companions":[]}
{"id":78,"category":"Control Flow","question":"How do guard variables work in try blocks?","url":"https://ek9.io/qa/QA0078.html","alternatePhrasings":["Can I guard a try block with a variable declaration?","How does try with guard work in EK9?","What happens if a try guard is unset?","How do I use try-catch in EK9?","How does exception handling work in EK9?"],"answer":"In EK9, guard variables in try blocks combine variable creation with an isSet check before executing the try body. If the guard is unset, the try body is skipped, catch does NOT execute, but finally still runs.\n\nBASIC TRY GUARD\nUse the declaration operator (<-) to guard a try block:\n  try resource <- acquireResource()\n    useResource(resource)\n  catch\n    -> ex as Exception\n    handleError(ex)\n  finally\n    cleanup()\nIf acquireResource() returns unset, the try body is skipped. The catch block does NOT fire because no exception occurred. The finally block ALWAYS runs.\n\nGUARD VS EXCEPTION\nThis is the key distinction:\n  Guard unset: Resource unavailable. Try body skipped. No error.\n  Exception: Resource acquired but fails during use. Catch handles it.\nGuard failures are expected situations (optional resource not available). Exceptions are unexpected errors during processing.\n\nTRY EXPRESSION WITH GUARD\nTry can be an expression that returns a value:\n  result <- try config <- loadConfig()\n    <- rtn as String: \"default\"\n    rtn: config.host\n  catch\n    -> ex as Exception\n    rtn: \"error: \" + $ex\nIf the guard is unset, the expression evaluates to the default return value.\n\nFINALLY ALWAYS RUNS\nRegardless of whether the guard is set or unset, or whether an exception occurs, the finally block always executes. This is identical to Java's finally guarantee.\n\nSee Q74 for guards in if. See Q75 for guards in switch. See Q76 for guards in for loops. See Q77 for guards in while loops. See Q134 for basic try/catch without guards. See Q135 for try/catch/finally. See Q137 for try-with-resources.","ek9Example":"defines module qa.flow.guard.tryblock\n\n  defines record\n\n    DbConnection\n      url <- String()\n\n      default operator ?\n\n  defines function\n\n    openConnection()\n      <- rtn <- DbConnection()\n      rtn.url: \"jdbc:example:db\"\n\n    openFailedConnection()\n      <- rtn <- DbConnection()\n\n  defines program\n\n    GuardTryDemo()\n      stdout <- Stdout()\n\n      // === BASIC TRY GUARD (resource available) ===\n\n      try conn <- openConnection()\n        stdout.println(\"Connected to: \" + conn.url)\n      catch\n        -> ex as Exception\n        stdout.println(\"Error: \" + $ex)\n      finally\n        stdout.println(\"Cleanup after connection attempt\")\n\n      // === TRY GUARD WITH UNSET (try body skipped) ===\n\n      try conn <- openFailedConnection()\n        stdout.println(\"This should not print\")\n      catch\n        -> ex as Exception\n        stdout.println(\"Catch should not fire either\")\n      finally\n        stdout.println(\"Finally always runs even when guard is unset\")\n\n      // === TRY EXPRESSION WITH GUARD ===\n\n      result <- try conn <- openConnection()\n        <- rtn as String: \"no connection\"\n        rtn: \"connected to \" + conn.url\n      catch\n        -> ex as Exception\n        rtn: \"error\"\n\n      stdout.println(\"Result: \" + result)","migrationContext":"Java: No guard in try. Try-with-resources handles cleanup but cannot skip the try body if resource is unavailable. Must wrap: 'var r = acquire(); if (r != null) { try { use(r); } catch ... }'. Python: No guard in try. 'with' statement handles cleanup but cannot conditionally skip. Must wrap: 'r = acquire(); if r: try: use(r)'. Rust: No guard in try (Rust uses Result/Option instead of try/catch). Pattern matching with '?' operator is different. Go: No try/catch. Defers handle cleanup. Guard concept does not apply. EK9: 'try resource <- acquireResource()' combines resource acquisition, isSet check, and exception handling. Guard failure is silent (no exception), finally always runs.","keywords":["acquire","branch","catch","condition","control","declaration","error","exception","finally","flow","guard","handle","isset","migrate","null-safe","resource","safe","try"],"primaryTopics":[],"typicalErrors":[{"error":"E01072","correct":"try conn <- openConnection()\n        stdout.println(\"Connected to: \" + conn.url)\n      catch\n        -> ex as Exception\n        stdout.println(\"Error: \" + $ex)","incorrect":"conn <- openConnection()\n      if not conn?\n        return\n      try\n        stdout.println(\"Connected to: \" + conn.url)\n      catch\n        -> ex as Exception\n        stdout.println(\"Error: \" + $ex)","explanation":"EK9 has no return statement. Try guards combine resource acquisition and isSet checking. If the guard is unset, the try body is skipped safely without needing an early return. See ek9 -h E01072 for details."},{"error":"E01072","correct":"result <- try conn <- openConnection()\n        <- rtn as String: \"no connection\"\n        rtn: \"connected to \" + conn.url","incorrect":"try conn <- openConnection()\n        return \"connected to \" + conn.url","explanation":"EK9 has no return statement. Try expressions use a declared return variable that is assigned in the body and catch branches. See ek9 -h E01072 for details."}],"companions":[]}
{"id":79,"category":"Control Flow","question":"What is the guarded assignment operator :=? in EK9?","url":"https://ek9.io/qa/QA0079.html","alternatePhrasings":["How do I assign only if a variable is unset?","What does :=? do in EK9?","How do I set a default value for an unset variable?","What is conditional assignment in EK9?"],"answer":"The guarded assignment operator (:=?) assigns a value ONLY if the target variable is currently UNSET. If the variable already has a value, the assignment is silently skipped.\n\nBASIC USAGE\nAssign only when the variable has no value:\n  name <- String()\n  name :=? \"Default Name\"\n  name :=? \"Second Attempt\"\nAfter these lines, name is \"Default Name\". The second :=? is skipped because name is already set.\n\nSETTING DEFAULTS\nThe most common use is providing fallback values:\n  config <- loadFromFile()\n  config :=? loadFromEnvironment()\n  config :=? hardcodedDefault()\nThis creates a priority chain: file config wins, then environment, then hardcoded default. Only the first successful source is used.\n\nDISTINCTION FROM OTHER OPERATORS\nEK9 has three assignment-family operators with different guard semantics:\n  :=  assigns unconditionally (blind assignment)\n  :=? assigns only if TARGET is currently UNSET\n  ?=  assigns only if SOURCE (RHS) is SET\n\nThe key difference: :=? checks the LEFT side (target). ?= checks the RIGHT side (source).\n\nIN CONTROL FLOW\nThe :=? operator also works in control flow guards:\n  if existing :=? getValue() with existing > 10\n    process(existing)\nHere, existing is only assigned if it was previously unset. The 'with' condition is then checked.\n\n  try config :=? loadConfig()\n    useConfig(config)\nOnly loads if config was unset. Useful for lazy initialization in try blocks.\n\nLAZY INITIALIZATION PATTERN\nCombine :=? with functions for lazy evaluation:\n  cache <- String()\n  cache :=? expensiveComputation()\nThe computation only runs if cache is unset. This is a clean lazy-init pattern without explicit if checks.\n\nSee Q22 for variable declaration. See Q29 for unset variables. See Q74 for guard variables in if. See Q78 for guards in try blocks. See Q104 for uninitialised properties in classes. See Q243 for coalescing operators (??, ?:) as alternatives to guarded assignment for handling unset values. See Q286 for first-wins accumulation with guarded assignment.","ek9Example":"defines module qa.flow.guard.assignment\n\n  defines function\n\n    loadPrimary()\n      <- rtn <- String()\n\n    loadFallback()\n      <- rtn <- String()\n      rtn: \"fallback-value\"\n\n    loadDefault()\n      <- rtn <- String()\n      rtn: \"hardcoded-default\"\n\n  defines program\n\n    GuardedAssignmentDemo()\n      stdout <- Stdout()\n\n      // === BASIC :=? USAGE ===\n\n      name <- String()\n      nameStatus <- \"unset\"\n      if name?\n        nameStatus: \"set\"\n      stdout.println(\"Before: name is \" + nameStatus)\n\n      name :=? \"Default Name\"\n      stdout.println(\"After first :=? name is: \" + name)\n\n      name :=? \"Second Attempt\"\n      stdout.println(\"After second :=? name is still: \" + name)\n\n      // === PRIORITY CHAIN PATTERN ===\n\n      config <- String()\n      config :=? loadPrimary()\n      config :=? loadFallback()\n      config :=? loadDefault()\n      stdout.println(\"Config resolved to: \" + config)\n\n      // === ALREADY SET VARIABLE ===\n\n      greeting <- loadPrimary()\n      greeting :=? \"Goodbye\"\n      stdout.println(\"Greeting stayed: \" + greeting)","migrationContext":"Java: No direct equivalent. Must write: 'if (value == null) { value = getDefault(); }'. Verbose and error-prone. Optional.orElse() handles one level but not chains. Python: No direct equivalent. 'value = value or default' is common but broken for falsy values (0, empty string). 'if value is None: value = default' is correct but verbose. Rust: No direct equivalent. Option::get_or_insert() modifies in place but requires mut. Chaining defaults requires nested unwrap_or_else(). Go: No direct equivalent. 'if v == nil { v = getDefault() }' required. No compound operator. Kotlin: Elvis operator '?:' handles null: 'value = value ?: default'. Close but only for null, not general isSet. EK9: 'value :=? default' is a single operator that checks the target's isSet state. Works with any type, chains naturally for priority fallbacks.","keywords":["assignment","branch","chain","condition","conditional","control","default","fallback","flow","guarded","initialize","isset","lazy","null-safe","operator","priority","safe","unset"],"primaryTopics":["guarded assignment","conditional assignment"],"typicalErrors":[{"error":"E50050","correct":"config :=? loadPrimary()\n      config :=? loadFallback()\n      config :=? loadDefault()","incorrect":"config <- loadPrimary()\n      if config?\n        return config\n      config <- loadFallback()\n      if config?\n        return config\n      config <- loadDefault()","explanation":"EK9 has no return statement. The guarded assignment operator :=? creates a clean priority chain pattern without needing early returns after each check. See ek9 -h E50050 for details."}],"companions":[]}
{"id":80,"category":"Control Flow","question":"Can a for-range loop return a value (for-range as expression)?","url":"https://ek9.io/qa/QA0080.html","alternatePhrasings":["How do I use a for-range loop as an expression in EK9?","How do I accumulate a result from a counting loop?","How does the for-range expression form work?"],"answer":"Yes. A for-range loop can return a value by declaring a return variable inside the loop body. The return variable is updated across iterations and its final value becomes the expression result.\n\nBASIC FOR-RANGE EXPRESSION\nAccumulate a sum from 1 to 5:\n  total <- for i in 1 ... 5\n    <- rtn <- 0\n    rtn: rtn + i\nThe outer '<- total' captures the loop result. The inner '<- rtn <- 0' declares the return variable initialised to 0. Each iteration adds to 'rtn'. After the loop completes, the final value of 'rtn' (15) is assigned to 'total'.\n\nRETURN VARIABLE DECLARATION\nThe return variable appears right after the for-range line, indented:\n  result <- for i in 1 ... limit\n    <- rtn <- 0\n    rtn: rtn + i\nThe '<- rtn <- 0' creates a variable named 'rtn' of type Integer (inferred from the literal 0), initialised to 0. You can also use typed declaration: '<- rtn as Integer: 0'.\n\nBUILDING STRINGS\nConcatenate values across iterations:\n  countdown <- for i in 5 ... 1 by -1\n    <- rtn <- \"\"\n    if length rtn > 0\n      rtn: rtn + \", \"\n    rtn: rtn + $i\nResult: '5, 4, 3, 2, 1'.\n\nWITH STEP\nExpression form works with the 'by' keyword:\n  sumEvens <- for i in 2 ... 10 by 2\n    <- rtn <- 0\n    rtn: rtn + i\nResult: 30 (2+4+6+8+10).\n\nSTATEMENT VS EXPRESSION\nThe statement form (see Q64) executes code for side effects. The expression form computes and returns a value. Use expression form when the loop's purpose is to produce a single result.\n\nEK9's for-range expression eliminates the need for external accumulator variables that are common in other languages. The return variable is scoped to the loop, making the intent clear.\n\nSee Q64 for for-range statement form. See Q68 for switch expression form. See Q81 for while/do-while expressions. See Q82 for for-in expression. See Q122 for stream collect as aggregation (alternative to loop accumulation). See Q237 for streams vs loops decision guide. See Q295 for single-letter loop counter conventions.","ek9Example":"defines module qa.flow.range.expression\n\n  defines program\n\n    ForRangeExpressionDemo()\n      stdout <- Stdout()\n\n      // === BASIC ACCUMULATION ===\n\n      total <- for i in 1 ... 5\n        <- rtn <- 0\n        rtn: rtn + i\n      stdout.println(`Sum 1..5: ${total}`)\n\n      // === BUILDING A STRING ===\n\n      countdown <- for i in 5 ... 1 by -1\n        <- rtn <- \"\"\n        if length rtn > 0\n          rtn: rtn + \", \"\n        rtn: rtn + $i\n      stdout.println(`Countdown: ${countdown}`)\n\n      // === SUM EVEN NUMBERS ===\n\n      sumEvens <- for i in 2 ... 10 by 2\n        <- rtn <- 0\n        rtn: rtn + i\n      stdout.println(`Sum evens 2..10: ${sumEvens}`)\n\n      // === FACTORIAL USING RANGE ===\n\n      factorial <- for i in 1 ... 6\n        <- rtn <- 1\n        rtn: rtn * i\n      stdout.println(`6! = ${factorial}`)","migrationContext":"Java: no for-loop expression form, must declare accumulator outside loop. Python: list comprehension or functools.reduce() for accumulation. Rust: iterators with .fold() or .sum() or .collect(), (1..=5).sum::<i32>(). Go: no loop expression, must use external accumulator. Kotlin: (1..5).fold(0) { acc, i -> acc + i } or sumOf for numeric. C#: Enumerable.Range(1, 5).Sum() or .Aggregate() for general fold. JavaScript: Array.from({length: 5}, (_, i) => i + 1).reduce((a, b) => a + b). Swift: (1...5).reduce(0, +) for sum. EK9: for-range expression with declared return variable, accumulator scoped to loop, no external variable needed, works with Integer, Float, Duration ranges.","keywords":["accumulate","branch","compute","condition","control","expression","flow","fold","for","range","reduce","sum","value"],"primaryTopics":[],"typicalErrors":[{"error":"E01070","correct":"total <- for i in 1 ... 5\n        <- rtn <- 0\n        rtn: rtn + i","incorrect":"total <- 0\n      for i in 1 ... 5\n        total: total + i\n        if total > 10\n          break","explanation":"EK9 has no break statement. For-range expression form scopes the accumulator inside the loop and always runs to completion. Use stream pipelines if early termination is needed. See ek9 -h E01070 for details."},{"error":"E01072","correct":"factorial <- for i in 1 ... 6\n        <- rtn <- 1\n        rtn: rtn * i","incorrect":"factorial <- 1\n      for i in 1 ... 6\n        factorial: factorial * i\n      return factorial","explanation":"EK9 has no return statement. For-range expressions return the final value of the declared return variable implicitly. See ek9 -h E01072 for details."}],"companions":[]}
{"id":81,"category":"Control Flow","question":"Can while and do-while loops return values (loop expressions)?","url":"https://ek9.io/qa/QA0081.html","alternatePhrasings":["How do I use a while loop as an expression in EK9?","Can a do-while loop return a value in EK9?","How do while and do-while expression forms work?"],"answer":"Yes. In EK9, both while and do-while loops can return values by declaring a return variable inside the loop body. The pattern is the same as for-range expressions.\n\nWHILE EXPRESSION\nAccumulate a sum using a while expression with a guard variable:\n  result <- while counter <- 0 with counter < limit\n    <- rtn <- 0\n    rtn: rtn + counter\n    counter: counter + 1\nThe outer '<- result' captures the loop result. 'counter <- 0' is a guard variable initialised to 0. 'with counter < limit' is the condition. The inner '<- rtn <- 0' declares the return variable. After the loop completes, the final value of 'rtn' is assigned to 'result'.\n\nDO-WHILE EXPRESSION\nThe do-while expression form guarantees the body runs at least once:\n  result <- do counter <- 0\n    <- rtn <- 0\n    rtn: rtn + counter\n    counter: counter + 1\n  while counter < limit\nThe body executes first, then 'counter < limit' is checked. The inner '<- rtn <- 0' declares the return variable.\n\nWHILE VS DO-WHILE\nwhile expression: Condition checked BEFORE each iteration. If condition is immediately false, the return variable keeps its initial value.\ndo-while expression: Body executes ONCE before condition check. Return variable always reflects at least one iteration.\n\nSIMPLE WHILE EXPRESSION\nWithout a guard variable (using variables from outer scope):\n  n <- 1\n  sum <- while n <= 10\n    <- rtn <- 0\n    rtn: rtn + n\n    n: n + 1\n\nWHEN TO USE LOOP EXPRESSIONS\nUse loop expressions when the loop's purpose is to compute a single result: sums, products, string building, search results. Use the statement form (Q66, Q67) when the loop performs side effects like I/O.\n\nSee Q66 for while statement form. See Q67 for do-while statement form. See Q80 for for-range expression. See Q82 for for-in expression. See Q68 for switch expression.","ek9Example":"defines module qa.flow.while.expression\n\n  defines function\n\n    supplyLimit() as pure\n      -> initial as Integer\n      <- rtn as Integer: initial\n\n  defines program\n\n    WhileExpressionDemo()\n      stdout <- Stdout()\n\n      // === WHILE EXPRESSION ===\n\n      limit <- supplyLimit(5)\n      sumResult <- while counter <- 0 with counter < limit\n        <- rtn <- 0\n        rtn: rtn + counter\n        counter: counter + 1\n      stdout.println(`While sum 0..4: ${sumResult}`)\n\n      // === DO-WHILE EXPRESSION ===\n\n      doResult <- do counter <- 0\n        <- rtn <- 0\n        rtn: rtn + counter\n        counter: counter + 1\n      while counter < limit\n      stdout.println(`Do-while sum 0..4: ${doResult}`)\n\n      // === DO-WHILE AT-LEAST-ONCE ===\n\n      // Even with limit=0, do-while body runs once\n      zeroLimit <- supplyLimit(0)\n      atLeastOnce <- do counter <- 1\n        <- rtn <- 0\n        rtn: rtn + counter\n        counter: counter + 1\n      while counter < zeroLimit\n      stdout.println(`At-least-once result: ${atLeastOnce}`)\n\n      // === BUILDING A STRING ===\n\n      expectedLength <- supplyLimit(4)\n      builtString <- while n <- 1 with n <= expectedLength\n        <- rtn <- \"\"\n        if length rtn > 0\n          rtn: rtn + \"-\"\n        rtn: rtn + $n\n        n: n + 1\n      stdout.println(`Built: ${builtString}`)","migrationContext":"Java: no while/do-while expression form, must declare accumulator outside loop. Python: no while expression, use list comprehension or itertools for functional accumulation. Rust: loop { break value } returns a value from loop, while and for do not directly return values. Go: no loop expression form, always use external accumulator. Kotlin: no while expression, use generateSequence().takeWhile().fold() for functional equivalent. C#: no while expression, use LINQ for functional accumulation. JavaScript: no while expression, use Array methods for functional patterns. Swift: no while expression, use sequence/reduce patterns. EK9: while and do-while expression with declared return variable, guard variables for loop initialisation, accumulator scoped to loop body.","keywords":["accumulate","branch","compute","condition","control","counter","do","expression","flow","guard","isset","null-safe","safe","value","while"],"primaryTopics":[],"typicalErrors":[{"error":"E01072","correct":"sumResult <- while counter <- 0 with counter < limit\n        <- rtn <- 0\n        rtn: rtn + counter\n        counter: counter + 1","incorrect":"total <- 0\n      counter <- 0\n      while counter < limit\n        total: total + counter\n        counter: counter + 1\n      return total","explanation":"EK9 has no return statement. While expressions use a declared return variable inside the loop body. The final value is returned implicitly. See ek9 -h E01072 for details."},{"error":"E01070","correct":"doResult <- do counter <- 0\n        <- rtn <- 0\n        rtn: rtn + counter\n        counter: counter + 1\n      while counter < limit","incorrect":"total <- 0\n      counter <- 0\n      do\n        total: total + counter\n        counter: counter + 1\n        if counter > 3\n          break\n      while counter < limit","explanation":"EK9 has no break statement. Do-while expressions always run until the condition is false. Structure the while condition to control termination. See ek9 -h E01070 for details."}],"companions":[]}
{"id":82,"category":"Control Flow","question":"Can a for-in loop return a value (for-in as expression)?","url":"https://ek9.io/qa/QA0082.html","alternatePhrasings":["How do I use a for-in loop as an expression in EK9?","How do I reduce a collection to a single value in EK9?","How does the for-in expression form work?"],"answer":"Yes. A for-in loop can return a value by declaring a return variable inside the loop body. This is EK9's equivalent of fold/reduce operations in functional languages.\n\nBASIC FOR-IN EXPRESSION\nConcatenate all items in a list:\n  joined <- for item in items\n    <- rtn <- \"\"\n    rtn: rtn + item\nThe outer '<- joined' captures the loop result. The inner '<- rtn <- \"\"' declares the return variable initialised to an empty string. Each iteration appends the current item to 'rtn'. After the loop completes, the final string is assigned to 'joined'.\n\nSUM A LIST OF NUMBERS\nReduce a list to its sum:\n  numbers <- [10, 20, 30, 40]\n  total <- for n in numbers\n    <- rtn <- 0\n    rtn: rtn + n\nResult: 100. This is the equivalent of numbers.stream().reduce(0, Integer::sum) in Java or numbers.iter().sum() in Rust.\n\nFIND MAXIMUM\nFind the largest value in a list:\n  largest <- for n in numbers\n    <- rtn <- Integer()\n    if ~rtn? or n > rtn\n      rtn: n\nThe return variable starts unset (Integer()). The first iteration sets it to the first element. Subsequent iterations update it only if the current element is larger.\n\nBUILDING WITH SEPARATOR\nJoin strings with a comma separator:\n  csv <- for name in names\n    <- rtn <- \"\"\n    if length rtn > 0\n      rtn: rtn + \", \"\n    rtn: rtn + name\nResult: 'Alice, Bob, Charlie'.\n\nSTATEMENT VS EXPRESSION\nThe statement form (see Q65) executes code for side effects like printing. The expression form computes a single result from the collection. Use expression form when the loop's purpose is to accumulate, reduce, or transform.\n\nSee Q65 for for-in statement form. See Q68 for switch expression. See Q80 for for-range expression. See Q81 for while/do-while expressions. See Q89 for stream pipeline basics. See Q122 for stream collect as aggregation. See Q237 for streams vs loops decision guide.","ek9Example":"defines module qa.flow.forin.expression\n\n  defines program\n\n    ForInExpressionDemo()\n      stdout <- Stdout()\n\n      // === CONCATENATE STRINGS ===\n\n      words <- [\"Hello\", \" \", \"World\"]\n      joined <- for word in words\n        <- rtn <- \"\"\n        rtn: rtn + word\n      stdout.println(`Joined: ${joined}`)\n\n      // === SUM NUMBERS ===\n\n      numbers <- [10, 20, 30, 40]\n      total <- for n in numbers\n        <- rtn <- 0\n        rtn: rtn + n\n      stdout.println(`Sum: ${total}`)\n\n      // === JOIN WITH SEPARATOR ===\n\n      names <- [\"Alice\", \"Bob\", \"Charlie\"]\n      csv <- for name in names\n        <- rtn <- \"\"\n        if length rtn > 0\n          rtn: rtn + \", \"\n        rtn: rtn + name\n      stdout.println(`Names: ${csv}`)\n\n      // === COUNT MATCHING ITEMS ===\n\n      scores <- [85, 42, 91, 67, 73, 95]\n      passingGrade <- 70\n      passing <- for s in scores\n        <- rtn <- 0\n        if s >= passingGrade\n          rtn: rtn + 1\n      stdout.println(`Passing scores: ${passing}`)","migrationContext":"Java: stream().reduce(identity, accumulator) or stream().collect() for fold/reduce patterns. Python: functools.reduce(fn, iterable, initial) or list comprehension. Rust: iterator.fold(init, |acc, x| acc + x) or .sum()/.collect(). Go: no fold primitive, must use for-range with external accumulator. Kotlin: list.fold(initial) { acc, item -> acc + item } or list.reduce(). C#: list.Aggregate(seed, (acc, x) => acc + x) LINQ method. JavaScript: array.reduce((acc, x) => acc + x, initial). Swift: array.reduce(0, +) or array.reduce(into:). EK9: for-in expression with declared return variable, natural fold/reduce pattern, accumulator scoped to loop body, no external variable needed.","keywords":["accumulate","branch","collection","compute","condition","control","expression","flow","fold","for","in","migrate","reduce","value"],"primaryTopics":[],"typicalErrors":[{"error":"E01072","correct":"total <- for n in numbers\n        <- rtn <- 0\n        rtn: rtn + n","incorrect":"total <- 0\n      for n in numbers\n        total: total + n\n      return total","explanation":"EK9 has no return statement. For-in expressions use a declared return variable scoped to the loop body. The final value is returned implicitly. See ek9 -h E01072 for details."}],"companions":[]}
{"id":83,"category":"Control Flow","question":"Which control flow constructs can be used as expressions in EK9?","url":"https://ek9.io/qa/QA0083.html","alternatePhrasings":["What loops can return values in EK9?","How do expression forms work across different control flow constructs?","What is the expression vs statement distinction in EK9 control flow?"],"answer":"EK9 allows six control flow constructs to be used as expressions that return values: switch, for-range, for-in, while, do-while, and try. All follow the same pattern: declare a return variable with '<-' inside the body.\n\nTHE UNIVERSAL PATTERN\nEvery expression form uses the same structure:\n  result <- CONTROL_FLOW_CONSTRUCT\n    <- rtn <- initialValue\n    ... body updates rtn ...\nThe outer '<- result' captures the expression value. The inner '<- rtn <- initialValue' declares and initialises the return variable. The body modifies 'rtn' across iterations or branches. The final value of 'rtn' becomes the result.\n\nSWITCH EXPRESSION (see Q68)\n  label <- switch priority\n    <- rtn as String?\n    case 1\n      rtn: \"HIGH\"\n    default\n      rtn: \"NORMAL\"\n\nFOR-RANGE EXPRESSION (see Q80)\n  total <- for i in 1 ... 10\n    <- rtn <- 0\n    rtn: rtn + i\n\nFOR-IN EXPRESSION (see Q82)\n  joined <- for item in items\n    <- rtn <- \"\"\n    rtn: rtn + item\n\nWHILE EXPRESSION (see Q81)\n  result <- while counter <- 0 with counter < limit\n    <- rtn <- 0\n    rtn: rtn + counter\n    counter: counter + 1\n\nDO-WHILE EXPRESSION (see Q81)\n  result <- do counter <- 0\n    <- rtn <- 0\n    rtn: rtn + counter\n    counter: counter + 1\n  while counter < limit\n\nSTATEMENT VS EXPRESSION FORM\nStatement form: performs side effects (I/O, mutation). No return variable.\n  for i in 1 ... 10\n    stdout.println($i)\n\nExpression form: computes and returns a value. Has return variable.\n  total <- for i in 1 ... 10\n    <- rtn <- 0\n    rtn: rtn + i\n\nIF IS NOT AN EXPRESSION\nUnlike the six constructs above, if/else in EK9 is always a statement. For a value chosen by a condition, use the TERNARY operator — 'grade <- score >= passMark <- \"A\" : \"B\"' (or 'else' in place of ':'), which is the if-expression equivalent (see Q1360) — or a switch expression for multi-way selection.\n\nWHY EXPRESSIONS MATTER\nExpression forms eliminate temporary accumulator variables that pollute the outer scope. The return variable is scoped to the construct, making intent clear and preventing accidental reuse.\n\nSee Q68 for switch expression. See Q80 for for-range expression. See Q81 for while/do-while expression. See Q82 for for-in expression.\n\nSee Q80 for for-range expressions. See Q81 for while expressions. See Q82 for for-in expressions.","ek9Example":"defines module qa.flow.expression.overview\n\n  defines function\n\n    supplyPriority() as pure\n      -> level as Integer\n      <- rtn as Integer: level\n\n    supplyLimit() as pure\n      -> initial as Integer\n      <- rtn as Integer: initial\n\n  defines program\n\n    ExpressionOverviewDemo()\n      stdout <- Stdout()\n\n      // === SWITCH EXPRESSION ===\n\n      priority <- supplyPriority(2)\n      label <- switch priority\n        <- rtn as String?\n        case 1\n          rtn: \"HIGH\"\n        case 2\n          rtn: \"MEDIUM\"\n        default\n          rtn: \"LOW\"\n      stdout.println(`Priority: ${label}`)\n\n      // === FOR-RANGE EXPRESSION ===\n\n      total <- for i in 1 ... 5\n        <- rtn <- 0\n        rtn: rtn + i\n      stdout.println(`Sum 1..5: ${total}`)\n\n      // === FOR-IN EXPRESSION ===\n\n      words <- [\"EK9\", \" \", \"rocks\"]\n      joined <- for word in words\n        <- rtn <- \"\"\n        rtn: rtn + word\n      stdout.println(`Joined: ${joined}`)\n\n      // === WHILE EXPRESSION ===\n\n      limit <- supplyLimit(4)\n      whileResult <- while counter <- 0 with counter < limit\n        <- rtn <- 0\n        rtn: rtn + counter\n        counter: counter + 1\n      stdout.println(`While sum 0..3: ${whileResult}`)\n\n      // === DO-WHILE EXPRESSION ===\n\n      doResult <- do counter <- 0\n        <- rtn <- 0\n        rtn: rtn + counter\n        counter: counter + 1\n      while counter < limit\n      stdout.println(`Do-while sum 0..3: ${doResult}`)","migrationContext":"Java: switch expression (Java 14+), no loop expressions, Stream.reduce() for functional fold. Rust: all blocks are expressions, match/if/loop can all return values, for and while cannot. Go: no expression forms for any control flow. Kotlin: when is expression, no loop expressions, fold/reduce for functional patterns. Python: ternary expression and comprehensions, no loop expressions. C#: switch expression (C# 8+), no loop expressions, LINQ Aggregate for fold. JavaScript: ternary only, no loop or switch expressions. Swift: no loop expressions, switch not an expression. EK9: switch, for-range, for-in, while, and do-while all have expression forms with uniform return variable pattern.","keywords":["branch","condition","constant","control","expression","flow","form","loop","overview","pattern","return","statement","switch","value"],"primaryTopics":[],"typicalErrors":[{"error":"E01072","correct":"label <- switch priority\n        <- rtn as String?\n        case 1\n          rtn: \"HIGH\"\n        case 2\n          rtn: \"MEDIUM\"\n        default\n          rtn: \"LOW\"","incorrect":"switch priority\n        case 1\n          return \"HIGH\"\n        case 2\n          return \"MEDIUM\"\n        default\n          return \"LOW\"","explanation":"EK9 has no return statement. All five expression forms (switch, for-range, for-in, while, do-while) use declared return variables. The value is returned implicitly. See ek9 -h E01072 for details."},{"error":"E01070","correct":"total <- for i in 1 ... 5\n        <- rtn <- 0\n        rtn: rtn + i","incorrect":"total <- 0\n      for i in 1 ... 5\n        total: total + i\n        if total > 10\n          break","explanation":"EK9 has no break statement. For-range expressions scope the accumulator inside the loop and always run to completion. See ek9 -h E01070 for details."}],"companions":[]}
{"id":84,"category":"Getting Started","question":"What operations does Optional support in EK9?","url":"https://ek9.io/qa/QA0084.html","alternatePhrasings":["How do I compare Optionals in EK9?","What is the ternary guard pattern for Optional?","How do I copy or merge Optional values?"],"answer":"Optional supports comparison, copy, merge, contains, and the ternary guard pattern. These operations follow EK9's tri-state semantics: two unset Optionals are equal, set and unset are not equal.\n\nTERNARY GUARD\nExtract a value or use a default in a single expression:\n  value <- o? <- o.get() else String()\nIf 'o' is set, evaluates to o.get(); otherwise uses the default.\n\nCONTAINS\nCheck if an Optional holds a specific value:\n  item contains 42                    true if set AND value equals 42\nAn unset Optional never contains anything.\n\nCOMPARISON\n  opt1 == opt2                        equal if both set with same value, or both unset\n  opt1 <> opt2                        not equal\n\nCOPY AND MERGE\n  copied :=: original                 deep copy\n  target :~: source                   merge: sets target only if source is set\nMerge is useful for layered defaults.\n\nNO REASSIGNMENT IN SAFE BLOCKS\nOnce inside a guard, you cannot reassign the Optional:\n  if o?\n    o: Optional(\"other\")             COMPILER ERROR\nThis prevents invalidating the safety guarantee.\n\nSTRING, JSON, AND HASHCODE\n  $item                               string representation\n  $$item                              JSON representation\n  #? item                             hashcode\n\nSee Q47 for Optional basics (creation, guards, getOrDefault). See Q85 for Optional in stream pipelines. See Q29 for tri-state semantics. See Q98 for record copy, merge, and replace operators.","ek9Example":"defines module qa.optional.operations\n\n  defines program\n    OptionalOperations()\n      stdout <- Stdout()\n\n      a <- Optional(10)\n      b <- Optional(10)\n      c <- Optional(99)\n      none <- Optional() of Integer\n\n      // === TERNARY GUARD ===\n\n      ternarySet <- a? <- a.get() else 0\n      stdout.println(`Ternary (set): ${ternarySet}`)\n\n      ternaryNone <- none? <- none.get() else 0\n      stdout.println(`Ternary (empty): ${ternaryNone}`)\n\n      // === CONTAINS ===\n\n      minQuantity <- 10\n      maxQuantity <- 99\n      stdout.println(`a contains 10: ${a contains minQuantity}`)\n      stdout.println(`a contains 99: ${a contains maxQuantity}`)\n      stdout.println(`none contains 10: ${none contains minQuantity}`)\n\n      // === COMPARISON ===\n\n      stdout.println(`a == b: ${a == b}`)\n      stdout.println(`a <> c: ${a <> c}`)\n      stdout.println(`a <> none: ${a <> none}`)\n\n      // === COPY ===\n\n      copied <- Optional() of Integer\n      copied :=: a\n      require copied == a\n      stdout.println(`Copied: ${copied}`)\n\n      // === MERGE ===\n\n      target <- Optional() of Integer\n      target :~: Optional(77)\n      stdout.println(`Merged: ${target}`)\n\n      // Merge with unset source — target unchanged\n      target2 <- Optional(5)\n      target2 :~: Optional() of Integer\n      stdout.println(`Merge no-op: ${target2}`)\n\n      // === STRING, JSON, HASHCODE ===\n\n      require a?\n      stdout.println(`String: ${a}`)\n      stdout.println(`JSON: ${$ a}`)\n      stdout.println(`Hash: ${#? a}`)","migrationContext":"Java: Optional.equals() for comparison, no copy/merge, .map()/.flatMap() for transformations. Rust: Option implements PartialEq, Clone for copy, no merge concept. Kotlin: == on nullable types uses structural equality, no merge. Go: no Optional type, manual nil checks. EK9: full operator set (==, <>, :=:, :~:, contains, $, $$, #?) with consistent tri-state semantics and compile-time guard enforcement.","keywords":["absent","beginner","comparison","contains","copy","first","guard","hashcode","intro","isset","json","merge","null-safe","operations","optional","reassignment","safe","start","string","ternary"],"primaryTopics":["optional operations","optional methods"],"typicalErrors":[{"error":"E08040","correct":"      require a?\n      stdout.println(`String: ${a}`)","incorrect":"if a?\n        a :=: Optional(5)\n        stdout.println(`String: ${$a}`)","explanation":"Reassigning a variable inside a guard block invalidates the safety check. This triggers E08040 — reassignment/mutation within possible safe method access scope is not allowed. The guard guarantees a is set; reassigning could make it unset. See ek9 -h E08040 for details."},{"error":"E08030","correct":"ternarySet <- a? <- a.get() else 0","incorrect":"ternarySet <- a.get()","explanation":"Accessing .get() without a ? guard triggers E08030 — has not been checked before access. Always check with ? or use getOrDefault() to avoid needing a guard. See ek9 -h E08030 for details."}],"companions":[]}
{"id":85,"category":"Getting Started","question":"How do I use Optional in stream pipelines?","url":"https://ek9.io/qa/QA0085.html","alternatePhrasings":["How does flatten work with Optional in EK9?","How do I extract present values from a list of Optionals?","Can Optional be used as an iterator?"],"answer":"Optional integrates with EK9's stream pipelines through the flatten operator. A List of Optional values can be flattened to extract only present values, discarding empty ones.\n\nFLATTEN\nExtract present values from a list of Optionals:\n  optionals <- [Optional(5), Optional() of Integer, Optional(14)]\n  cat optionals | flatten > stdout\nThe empty Optional contributes nothing. Only 5 and 14 pass through.\n\nCOLLECT WITH FLATTEN\nAggregate present values:\n  sum <- cat optionals | flatten | collect as Integer\nSum equals 19. This is EK9's equivalent of Java's Stream.flatMap(Optional::stream) but more intuitive.\n\nOPTIONAL AS ITERATOR\nOptional supports the iterator protocol, yielding 0 or 1 elements:\n  present <- Optional(\"Hello\")\n  while iter <- present.iterator() then iter.hasNext()\n    stdout.println($iter.next())\nAn empty Optional's iterator yields nothing. This makes Optional compatible with any code that expects an iterable.\n\nSee Q47 for Optional basics (creation, guards, getOrDefault). See Q84 for Optional operations (comparison, copy, merge). See Q59 for function pipelines. See Q123 for flattening nested collections and lists of lists.","ek9Example":"defines module qa.optional.streams\n\n  defines program\n    OptionalStreams()\n      stdout <- Stdout()\n\n      // === FLATTEN ===\n\n      // Extract present values from list of Optionals\n      optionals <- [Optional(5), Optional() of Integer, Optional(14)]\n      cat optionals | flatten > stdout\n\n      // === COLLECT WITH FLATTEN ===\n\n      // Sum only the present values\n      sum <- cat optionals | flatten | collect as Integer\n      require sum == 19\n      stdout.println(`Sum of present values: ${sum}`)\n\n      // Inline form\n      mixed <- [Optional(10), Optional() of Integer, Optional(20)]\n      sum2 <- cat mixed | flatten | collect as Integer\n      stdout.println(`Sum2: ${sum2}`)\n\n      // === OPTIONAL AS ITERATOR ===\n\n      // Present Optional yields one element\n      present <- Optional(\"Hello\")\n      while iter <- present.iterator() then iter.hasNext()\n        stdout.println(`Iterator: ${iter.next()}`)\n\n      // Empty Optional yields nothing\n      emptyOpt <- Optional() of String\n      while iter <- emptyOpt.iterator() then iter.hasNext()\n        stdout.println(\"Should not print\")\n\n      stdout.println(\"Done\")","migrationContext":"Java: Stream.flatMap(Optional::stream) to extract present values, verbose compared to EK9's flatten. Rust: .filter_map(|x| x) or .flatten() on Iterator<Option<T>>. Kotlin: .filterNotNull() on lists of nullable types. Python: list comprehension [x for x in items if x is not None]. EK9: cat optionals | flatten — single intuitive operator extracts present values from any list of Optionals.","keywords":["absent","aggregate","beginner","collect","extract","first","flatten","guard","intro","iterator","migrate","optional","pipeline","present","safe","start","stream"],"primaryTopics":[],"typicalErrors":[{"error":"E07840","correct":"cat mixed | flatten | collect as Integer","incorrect":"cat sum | flatten | collect as Integer","explanation":"The 'cat' operation requires an iterable source (List, Dict, Range, Optional, etc.). Using a non-iterable Integer value triggers E07840 — cannot iterate over that type. See ek9 -h E07840 for details."},{"error":"E50060","correct":"present.iterator()","incorrect":"present.stream()","explanation":"EK9 Optional does not have a Java-style '.stream()' method. Use '.iterator()' to get an iterator that yields 0 or 1 elements. See ek9 -h E50060 for details."}],"companions":[]}
{"id":86,"category":"Getting Started","question":"How do Result guard patterns and ternary access work?","url":"https://ek9.io/qa/QA0086.html","alternatePhrasings":["How does the dual guard pattern work with Result isOk and isError?","How do I use ternary Result access to get ok or error values with defaults?","What are the safe access patterns for Result values in EK9?"],"answer":"In EK9, Result guard patterns let you safely access ok and error values through compiler-enforced checks. The key insight is that isOk() and isError() are INDEPENDENT guards: isOk() unlocks .ok(), isError() unlocks .error(), and neither unlocks the other.\n\nDUAL GUARD PATTERN\nCheck both sides of a Result independently using if/else if:\n  if r.isOk()\n    okVal <- r.ok()\n  else if r.isError()\n    errVal <- r.error()\nThe compiler tracks which guard is active. Inside the isOk() branch, only .ok() is permitted. Inside the isError() branch, only .error() is permitted. Using the wrong accessor is a compile error.\n\n? OPERATOR GUARDS OK ONLY\nThe ? operator is shorthand for isOk(), so it only unlocks .ok():\n  if r?\n    okVal <- r.ok()\nYou cannot call .error() inside a ? guard block. For error access, you must use isError() explicitly.\n\nTERNARY GUARD PATTERNS\nGet a value or a default in a single expression:\n  okVal <- r.isOk() <- r.ok() else String()\n  errVal <- r.isError() <- r.error() else Integer()\nThe ternary reads as: declare okVal, check isOk(), if true assign r.ok(), otherwise assign the default. This is concise and safe. The compiler verifies the guard matches the accessor.\n\nAND LOGIC WITH MULTIPLE RESULTS\nCombine guards across multiple Results with 'and':\n  if r1? and r2.isError()\n    okVal <- r1.ok()\n    errVal <- r2.error()\nEach guard independently unlocks its own Result. The block only runs if ALL conditions pass.\n\nNO REASSIGNMENT IN SAFE BLOCKS\nOnce a Result is guarded, the compiler prevents reassignment inside the safe block:\n  if r?\n    r: otherResult    COMPILER ERROR: NO_REASSIGNMENT_WITHIN_SAFE_ACCESS\nThis prevents invalidating the guard check by swapping the Result for one that might not be ok.\n\nSee Q48 for Result basics and creation patterns. See Q87 for Result operations (merge, copy, callbacks). See Q74 for guard patterns in if statements. See Q139 for when to use Result vs try/catch exception handling. See Q259 for how EK9's four-state Result differs from other languages.","ek9Example":"defines module qa.result.guards\n\n  defines function\n\n    <?-\n      Returns a Result with ok value set.\n    -?>\n    getOkResult()\n      <- rtn <- Result(\"Success\", Integer())\n\n    <?-\n      Returns a Result with error value set.\n    -?>\n    getErrorResult()\n      <- rtn <- Result(String(), 42)\n\n    <?-\n      Returns a Result with both ok and error set.\n    -?>\n    getBothResult()\n      <- rtn <- Result(\"Partial\", -1)\n\n  defines program\n\n    ResultGuardDemo()\n      stdout <- Stdout()\n\n      // === DUAL GUARD PATTERN ===\n\n      r1 <- getOkResult()\n      if r1.isOk()\n        stdout.println(`Dual ok: ${r1.ok()}`)\n      else if r1.isError()\n        stdout.println(`Dual error: ${r1.error()}`)\n\n      r2 <- getErrorResult()\n      if r2.isOk()\n        stdout.println(`Should not print`)\n      else if r2.isError()\n        stdout.println(`Dual error side: ${r2.error()}`)\n\n      // === ? OPERATOR GUARDS OK ONLY ===\n\n      r3 <- getOkResult()\n      if r3?\n        stdout.println(`? guard ok: ${r3.ok()}`)\n\n      r4 <- getErrorResult()\n      if ~r4?\n        stdout.println(\"Error result: ? is false as expected\")\n\n      // === isOk GUARDS ok, isError GUARDS error — INDEPENDENT ===\n\n      r5 <- getBothResult()\n      if r5.isOk()\n        stdout.println(`Both has ok: ${r5.ok()}`)\n      if r5.isError()\n        stdout.println(`Both has error: ${r5.error()}`)\n\n      // === TERNARY GUARD: ok with default ===\n\n      r6 <- getOkResult()\n      okVal <- r6.isOk() <- r6.ok() else String()\n      stdout.println(`Ternary ok: ${okVal}`)\n\n      // === TERNARY GUARD: error with default ===\n\n      r7 <- getErrorResult()\n      errVal <- r7.isError() <- r7.error() else Integer()\n      stdout.println(`Ternary error: ${errVal}`)\n\n      // Ternary on empty Result falls back to default\n      r8 <- Result() of (String, Integer)\n      fallback <- r8.isOk() <- r8.ok() else \"default\"\n      stdout.println(`Ternary fallback: ${fallback}`)\n\n      // === AND LOGIC WITH MULTIPLE RESULTS ===\n\n      rOk <- getOkResult()\n      rErr <- getErrorResult()\n      if rOk? and rErr.isError()\n        stdout.println(`Combined: ok=${rOk.ok()}, error=${rErr.error()}`)\n\n      // Both-value Result: can guard both independently\n      rBoth <- getBothResult()\n      if rBoth.isOk() and rBoth.isError()\n        stdout.println(`Both sides: ${rBoth.ok()} with ${rBoth.error()}`)\n\n      // === SAFE BLOCK SCOPE ===\n      // Inside a guard block, the guarded Result cannot be reassigned.\n      // This prevents invalidating the safety guarantee.\n      // Attempting: r: otherResult inside if r? would produce\n      // COMPILER ERROR: NO_REASSIGNMENT_WITHIN_SAFE_ACCESS\n\n      r9 <- getOkResult()\n      if r9?\n        safeOk <- r9.ok()\n        stdout.println(`Safe access: ${safeOk}`)\n        // r9 is protected here — no reassignment allowed","migrationContext":"Java: try-catch blocks handle errors but have no compile-time enforcement that catch blocks are present for unchecked exceptions, no safe accessor pattern, can forget to handle the error path entirely. Rust: match on Result<T,E> with Ok(v)/Err(e) arms provides exhaustive checking, but .unwrap() is an escape hatch that panics at runtime, no independent ok+error access since Result is strictly either/or. Go: comma-ok idiom 'val, err := doThing(); if err != nil' is convention not enforcement, error can be silently ignored with _, no compiler-enforced safe access. EK9: isOk() and isError() are independent compiler-enforced guards, no escape hatches, ternary guard gives concise safe access with defaults, no reassignment rule prevents guard invalidation.","keywords":["access","beginner","dual","error","first","guard","intro","isError","isOk","isset","null-safe","ok","pattern","reassignment","result","safe","start","ternary"],"primaryTopics":[],"typicalErrors":[{"error":"E08030","correct":"if r3?\n        stdout.println(`? guard ok: ${r3.ok()}`)","incorrect":"stdout.println(`? guard ok: ${r3.ok()}`)","explanation":"Calling .ok() without an isOk() or ? guard triggers E08030 — has not been checked before access. The compiler enforces that isOk() must be checked before .ok(), and isError() before .error(). See ek9 -h E08030 for details."},{"error":"E08040","correct":"if r9?\n        safeOk <- r9.ok()","incorrect":"if r9?\n        r9 := getErrorResult()\n        safeOk <- r9.ok()","explanation":"Reassigning a Result variable inside a guard block triggers E08040 — reassignment/mutation within safe access scope is not allowed. The guard verified r9 was ok; reassigning invalidates that check. See ek9 -h E08040 for details."}],"companions":[]}
{"id":87,"category":"Getting Started","question":"What operations and callbacks does Result support?","url":"https://ek9.io/qa/QA0087.html","alternatePhrasings":["How do whenOk and whenError callbacks work on Result?","How do I compare or copy Result values in EK9?","How does the merge operator work with Result?"],"answer":"Result supports callbacks, factory methods, operators for comparison, copy, merge, and conversions beyond basic guard access.\n\nWHEN OK / WHEN ERROR CALLBACKS\n  r.whenOk(myConsumer)           called only if ok present\n  r.whenError(errorConsumer)     called only if error present\nConsumer is pure. Acceptor is non-pure (can mutate/do I/O). Callback never invoked if value absent.\n\nCONTAINS\n  r contains \"Steve\"             true if isOk AND ok equals argument\n\nITERATOR\nYields 0 or 1 elements over the ok value:\n  while iter <- r.iterator() then iter.hasNext()\n    item :=: iter.next()\n\nFACTORY METHODS\n  justOk <- r.asOk(\"Steve\")     ok only\n  justErr <- r.asError(-1)       error only\n  cleared <- r.asEmpty()         neither set\n\nMERGE (:~:)\nFills unset sides from another Result without overwriting:\n  r1 :~: r2\nIdeal for layering defaults.\n\nCOPY (:=:), COMPARISON (== <>)\nDeep copy: target :=: source. Two empty Results are equal.\n\nSTRING ($), JSON ($$), HASHCODE (#?)\n  $r, $$r, #? r\n\nSee Q48 for Result basics. See Q86 for Result guards. See Q54 for Consumer/Acceptor. See Q29 for tri-state. See Q139 for Result vs try/catch. See Q259 for four-state design.","ek9Example":"defines module qa.result.operations\n\n  defines function\n\n    <?-\n      Returns a Result with ok value set.\n    -?>\n    getOkResult()\n      <- rtn <- Result(\"Steve\", Integer())\n\n    <?-\n      Pure consumer for ok values.\n    -?>\n    okProcessor() as pure\n      -> okContent as String\n      require okContent?\n\n    <?-\n      Pure consumer for error values.\n    -?>\n    errorProcessor() as pure\n      -> code as Integer\n      require code?\n\n  defines program\n    ResultOperationsDemo()\n      stdout <- Stdout()\n\n      // === WHEN OK / WHEN ERROR CALLBACKS ===\n\n      // Pure Consumer callbacks — read-only\n      okOnly <- Result(\"Steve\", Integer())\n      okOnly.whenOk(okProcessor)\n      stdout.println(\"whenOk with Consumer: called on ok Result\")\n\n      errOnly <- Result(String(), -1)\n      errOnly.whenError(errorProcessor)\n      stdout.println(\"whenError with Consumer: called on error Result\")\n\n      // Callbacks on Result with both values\n      both <- Result(\"Default\", -1)\n      both.whenOk(okProcessor)\n      both.whenError(errorProcessor)\n      stdout.println(\"Both callbacks fired on dual-value Result\")\n\n      // Callback NOT fired when value absent\n      emptyResult <- Result() of (String, Integer)\n      emptyResult.whenOk(okProcessor)\n      emptyResult.whenError(errorProcessor)\n      stdout.println(\"Empty Result: neither callback fires\")\n\n      // === CONTAINS ===\n\n      stdout.println(`contains Steve: ${okOnly contains \"Steve\"}`)\n      stdout.println(`contains Other: ${okOnly contains \"Other\"}`)\n      stdout.println(`error contains: ${errOnly contains \"Steve\"}`)\n\n      // === ITERATOR ===\n\n      extracted <- String()\n      while iter <- okOnly.iterator() then iter.hasNext()\n        extracted :=: iter.next()\n      stdout.println(`Iterator ok: ${extracted}`)\n\n      // Empty Result iterator yields nothing\n      noItems <- String()\n      while iter <- emptyResult.iterator() then iter.hasNext()\n        noItems :=: iter.next()\n      stdout.println(`Iterator empty: ${noItems?}`)\n\n      // === FACTORY METHODS ===\n\n      prototype <- Result(\"Proto\", 99)\n\n      justOk <- prototype.asOk(\"Fresh\")\n      require justOk.isOk() and not justOk.isError()\n      stdout.println(`asOk: ${justOk}`)\n\n      justErr <- prototype.asError(42)\n      require not justErr.isOk() and justErr.isError()\n      stdout.println(`asError: ${justErr}`)\n\n      cleared <- prototype.asEmpty()\n      require cleared is empty\n      stdout.println(`asEmpty: ${cleared}`)\n\n      // === MERGE (:~:) ===\n\n      m1 <- Result(\"Steve\", Integer())\n      m2 <- Result(String(), -1)\n      m1 :~: m2\n      require m1.isOk() and m1.isError()\n      stdout.println(`Merged: ${m1}`)\n\n      // Merge does not overwrite existing ok\n      m3 <- Result(\"Keep\", Integer())\n      m4 <- Result(\"Overwrite\", -2)\n      m3 :~: m4\n      if m3.isOk()\n        stdout.println(`Merge kept ok: ${m3.ok()}`)\n\n      // === COPY (:=:) ===\n\n      original <- Result(\"Alice\", 7)\n      copied <- Result() of (String, Integer)\n      copied :=: original\n      require copied == original\n      stdout.println(`Copied: ${copied}`)\n\n      // === COMPARISON (== and <>) ===\n\n      c1 <- Result(\"A\", 1)\n      c2 <- Result(\"A\", 1)\n      c3 <- Result(\"B\", 2)\n      stdout.println(`c1 == c2: ${c1 == c2}`)\n      stdout.println(`c1 <> c3: ${c1 <> c3}`)\n\n      // Empty Results are equal\n      e1 <- Result() of (String, Integer)\n      e2 <- Result() of (String, Integer)\n      stdout.println(`empty == empty: ${e1 == e2}`)\n\n      // === STRING ($), JSON ($$), HASHCODE (#?) ===\n\n      display <- Result(\"Steve\", 42)\n      stdout.println(`String: ${display}`)\n      stdout.println(`JSON: ${$ display}`)\n      stdout.println(`Hash: ${#? display}`)","migrationContext":"Java: no Result, CompletableFuture callbacks no compile safety. Rust: map/and_then, no merge, either/or only. Go: (value, error) tuple, manual checks. EK9: whenOk/whenError callbacks, :~: merge, :=: copy, contains, iterator, factory methods, compile-time safety.","keywords":["acceptor","asEmpty","asError","asOk","beginner","callback","comparison","consumer","contains","copy","error","first","guard","hashcode","intro","iterator","json","merge","ok","operations","result","start","string","whenError","whenOk"],"primaryTopics":[],"typicalErrors":[{"error":"E08130","correct":"okProcessor() as pure\n      -> okContent as String\n      require okContent?","incorrect":"okProcessor() as pure\n      -> okContent as String\n      result <- getOkResult()\n      require okContent?","explanation":"Calling a non-pure function like getOkResult() from within a pure function triggers E08130 — not marked pure but call is made in a pure scope. Pure functions cannot call non-pure functions. See ek9 -h E08130 for details."},{"error":"E06190","correct":"Result(\"Steve\", Integer())","incorrect":"Result(\"Steve\", String())","explanation":"Result requires two different types for ok and error. Using the same type for both triggers E06190 — Result must be used with two different types. This ensures the compiler can distinguish ok from error. See ek9 -h E06190 for details."}],"companions":[]}
{"id":88,"category":"Getting Started","question":"What operations does List support in EK9?","url":"https://ek9.io/qa/QA0088.html","alternatePhrasings":["How do I check if an item is in a list in EK9?","How do I copy or merge lists in EK9?","How do I compare lists or convert them to JSON in EK9?"],"answer":"EK9 Lists support a rich set of operations beyond basic add/remove: membership testing, reverse, comparison, copy/merge, and conversion to String/JSON.\n\nMEMBERSHIP TESTING\nThree ways to check if an item is in a list:\n  numbers contains 3             operator syntax\n  \"Alice\" is in names            natural language syntax\n  \"Zara\" is not in names         negated form\nAll three return Boolean. The 'is in' and 'is not in' operators read like English.\n\nREVERSE\n  reversed <- numbers.reverse()   returns a new reversed list\nThe original list is unchanged.\n\nCOMPARISON\nLists compare element by element:\n  [1, 2, 3] == [1, 2, 3]    true (same elements, same order)\n  [1, 2, 3] <> [4, 5, 6]    true (different elements)\n\nCOPY AND MERGE\n  copied :=: original       deep copy of original into copied\n  target :~: \"Eve\"           merge a single item into target\n  target :~: otherList       merge all items from otherList into target\nCopy (:=:) replaces the contents entirely. Merge (:~:) appends to existing contents.\n\nSTRING AND JSON CONVERSION\n  $numbers                   string representation: [1, 2, 3]\n  $$numbers                  JSON array: [1, 2, 3]\nThe $$ operator converts any list to a JSON array.\n\nHASHCODE\n  hash <- #? numbers         integer hash of the list contents\n\nEMPTY LIST IS SET (NOT UNSET)\nCritical semantics: an empty list IS set. List() of String creates a valid, set, empty list. An empty list is meaningful, not unknown. Only explicit unSet() makes a list unset.\n\nSee Q45 for List basics (creation, add, remove, access). See Q89 for stream pipelines (filter, map, collect). See Q29 for the tri-state model (absent/unset/set). See Q130 for mutating vs non-mutating operator deep dive. See Q186 for merge collections. See Q187 for reverse list.","ek9Example":"defines module qa.list.operations\n\n  defines program\n    ListOperationsDemo()\n      stdout <- Stdout()\n\n      // === MEMBERSHIP TESTING ===\n\n      numbers <- [1, 2, 3, 4, 5]\n      minTagLength <- 3\n      hasThree <- numbers contains minTagLength\n      stdout.println(`Contains 3: ${hasThree}`)\n\n      names <- [\"Alice\", \"Bob\", \"Charlie\"]\n\n      if \"Alice\" is in names\n        stdout.println(\"Found Alice\")\n\n      if \"Zara\" is not in names\n        stdout.println(\"Zara not found\")\n\n      // === REVERSE ===\n\n      reversed <- numbers.reverse()\n      stdout.println(`Reversed: ${reversed}`)\n      stdout.println(`Original unchanged: ${numbers}`)\n\n      // === COMPARISON ===\n\n      l1 <- [1, 2, 3]\n      l2 <- [1, 2, 3]\n      l3 <- [4, 5, 6]\n\n      stdout.println(`l1 == l2: ${l1 == l2}`)\n      stdout.println(`l1 <> l3: ${l1 <> l3}`)\n\n      // === COPY AND MERGE ===\n\n      copied <- List() of Integer\n      copied :=: numbers\n      stdout.println(`Copied: ${copied}`)\n\n      // Merge single item\n      names :~: \"Dave\"\n      stdout.println(`After merge item: ${names}`)\n\n      // Merge list into list\n      extras <- [\"Eve\", \"Frank\"]\n      names :~: extras\n      stdout.println(`After merge list: ${names}`)\n\n      // === STRING AND JSON CONVERSION ===\n\n      asString <- $numbers\n      stdout.println(`As string: ${asString}`)\n\n      stdout.println(`As JSON: ${$ numbers}`)\n\n      // === HASHCODE ===\n\n      hash <- #? numbers\n      stdout.println(`Hash: ${hash}`)\n\n      // === EMPTY LIST IS SET ===\n\n      emptyList <- List() of String\n      require emptyList?\n      require emptyList is empty\n      stdout.println(`Empty list isSet: ${emptyList?}`)\n      stdout.println(`Empty list isEmpty: ${emptyList is empty}`)","migrationContext":"Java: Collections.reverse() modifies in place, List.contains() for membership, no 'is in' syntax, equals() for comparison, no built-in JSON. Python: 'in' operator for membership, reversed() returns iterator, == for comparison, json.dumps() for JSON. JavaScript: includes() for membership, reverse() mutates, JSON.stringify() for JSON. Rust: contains() for membership, == via PartialEq, serde for JSON. Go: no contains (manual loop), no operator overloading. EK9: 'contains', 'is in', 'is not in' for membership, reverse() returns new list, == and <> for comparison, :=: copy, :~: merge, $ string, $$ JSON.","keywords":["beginner","comparison","contains","copy","first","hashcode","intro","is in","is not in","json","list","membership","merge","operations","reverse","start","string"],"primaryTopics":["list operations","list methods","list API"],"typicalErrors":[{"error":"E50060","correct":"reversed <- numbers.reverse()","incorrect":"reversed <- numbers.reversed()","explanation":"List has no reversed() method. The correct EK9 method is reverse(). Use 'ek9 -h List' to see the full API. See ek9 -h E50060 for details."}],"companions":[]}
{"id":89,"category":"Getting Started","question":"How do I use streams and stream pipelines in EK9?","url":"https://ek9.io/qa/QA0089.html","alternatePhrasings":["How do I filter and map a list in EK9?","What is the cat pipe collect pattern in EK9?","How do stream pipelines work with lists in EK9?","What replaces for loops with break in EK9?","What is the EK9 equivalent of Java Stream API?"],"answer":"EK9 lists use cat | pipe | collect syntax for stream pipelines, replacing Java Streams, Python comprehensions, and JS array methods.\n\nBASIC PATTERN: cat source | operations | terminal\n  cat numbers | filter by isEven | collect as List of Integer\n  cat numbers | map with doubleIt | collect as List of Integer\n  cat numbers | collect as Integer\n\nFILTER BY\n  evens <- cat numbers | filter by isEven | collect as List of Integer\nPredicate must be pure, returning Boolean.\n\nMAP WITH\n  doubled <- cat numbers | map with doubleIt | collect as List of Integer\n\nCOLLECT AS\n  List of T collects elements. Integer reduces by summation.\n\nDIRECT OUTPUT\n  cat numbers | map with intToString > stdout\n\nTEE FOR SIDE EFFECTS\n  cat numbers | tee in sideEffect | collect as List of Integer\n\nCOMBINING: stages chain naturally. Streams replace loops with break/continue (EK9 has none).\n\nSee Q45 for List basics. See Q51 for abstract pipeline functions. See Q52 for dynamic functions. See Q53 for closures. See Q54 for Predicate/Comparator/UnaryOperator. See Q55 for delegates. See Q59 for pipelines. See Q64 for for-range. See Q65 for for-in. See Q88 for List operations. See Q120 for sorting. See Q122 for custom collect. See Q124 for group by. See Q125 for head/tail/skip. See Q133 for tee/uniq. See Q235 for stream reference. See Q236 for custom iterators. See Q237 for streams vs loops. See Q265 for fluent API. See Q275 for AI break/continue. See Q284 for first match.","ek9Example":"defines module qa.list.streams\n\n  defines function\n\n    isEven() as pure\n      -> num as Integer\n      <- rtn as Boolean: num mod 2 == 0\n\n    doubleIt() as pure\n      -> num as Integer\n      <- rtn as Integer: num * 2\n\n    intToString() as pure\n      -> num as Integer\n      <- rtn as String: $num\n\n  defines program\n    ListStreamsDemo()\n      stdout <- Stdout()\n\n      numbers <- [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]\n      stdout.println(`Numbers: ${numbers}`)\n\n      // === FILTER BY ===\n\n      // Select even numbers\n      evens <- cat numbers | filter by isEven | collect as List of Integer\n      stdout.println(`Evens: ${evens}`)\n\n      // === MAP WITH ===\n\n      // Double each number\n      doubled <- cat numbers | map with doubleIt | collect as List of Integer\n      stdout.println(`Doubled: ${doubled}`)\n\n      // Transform to strings\n      asStrings <- cat numbers | map with intToString | collect as List of String\n      stdout.println(`As strings: ${asStrings}`)\n\n      // === COLLECT AS REDUCTION ===\n\n      // Sum all numbers\n      total <- cat numbers | collect as Integer\n      stdout.println(`Sum: ${total}`)\n\n      // === COMBINED PIPELINE ===\n\n      // Filter then map\n      doubledEvens <- cat numbers | filter by isEven | map with doubleIt | collect as List of Integer\n      stdout.println(`Doubled evens: ${doubledEvens}`)\n\n      // === DIRECT OUTPUT ===\n\n      // Pipe directly to stdout\n      cat numbers | map with intToString > stdout","migrationContext":"Java: Stream API, verbose Collectors. Python: list comprehensions. JS: .filter().map().reduce(). Go: manual loops. EK9: cat | filter by | map with | collect as, unified pipe syntax.","keywords":["async","beginner","cat","chain","collect","concurrency","filter","first","fluent","functional","intro","lazy","map","migrate","parallel","pipe","pipeline","sequence","start","stdout","stream","tee","transform"],"primaryTopics":["streams","stream pipeline","functional stream"],"typicalErrors":[{"error":"E07520","correct":"filter by isEven","incorrect":"filter by doubleIt","explanation":"The 'filter by' pipeline stage requires a predicate function returning Boolean. A function returning any other type triggers E07520 — filter function must return Boolean. See ek9 -h E07520 for details."},{"error":"E07830","correct":"map with intToString | collect as List of String\n      stdout.println(`As strings: ${asStrings}`)","incorrect":"map with intToString | collect as List of Integer\n      stdout.println(`As strings: ${asStrings}`)","explanation":"Each pipeline stage must accept the type produced by the previous stage. A type mismatch between stages triggers E07830 — pipeline type mismatch. See ek9 -h E07830 for details."}],"companions":[]}
{"id":90,"category":"Getting Started","question":"What operations does Dict support in EK9?","url":"https://ek9.io/qa/QA0090.html","alternatePhrasings":["How do I get keys and values from an EK9 Dict?","How do I check if a Dict contains a key in EK9?","How do I merge, copy, or compare Dicts in EK9?"],"answer":"Dict supports a rich set of operations beyond basic creation and lookup. This covers containment checks, key/value iterators, merging, copying, comparison, and conversions.\n\nCONTAINS (KEY CHECK)\nThree equivalent ways to check if a key exists:\n  ages contains \"Alice\"           operator syntax\n  \"Bob\" is in ages                natural language syntax\n  \"Zara\" is not in ages           negated form\nThese check keys, not values.\n\nKEYS AND VALUES ITERATORS\n  keyIter <- ages.keys()          iterator over all keys\n  valIter <- ages.values()        iterator over all values\nThese return iterators (not lists). Use in while loops:\n  while keyIter?\n    stdout.println(keyIter.next())\n\nMERGING DICTS\n+ creates a new merged dict (originals unchanged):\n  combined <- dict1 + dict2\n:~: merge operator (mutates the left-hand side):\n  dict1 :~: dict2\nWhen keys overlap, the right-hand side values win.\n\nCOPY AND REPLACE\n:=: deep copy:\n  copied :=: original\n:^: replace contents entirely:\n  target :^: source\n\nCOMPARISON\n  dict1 == dict2                  equal if same key-value pairs\n  dict1 <> dict2                  not equal\n\nSTRING AND JSON CONVERSION\n  $ages                           string representation\n  $$ages                          JSON object representation\nThe $$ operator converts Dict to JSON with keys as JSON keys and values as JSON values.\n\nHASHCODE\n  hash <- #? ages                 integer hashcode\n\nEMPTY DICT IS SET (NOT UNSET)\nCRITICAL: An empty dict IS set. Creating Dict() of (K, V) gives you a valid, set, empty dict. This follows EK9's collection semantics: collections are always meaningful when created, even if empty. An empty dict is not the same as a missing dict.\n  emptyDict <- Dict() of (String, Integer)\n  emptyDict?                      true (set)\n  emptyDict is empty              true (no entries)\n\nSee Q46 (How does the Dict type work?) for creation, DictEntry, adding/removing, getOrDefault, and basic iteration. See Q45 (How does the List type work?) for the sister collection type. See Q29 (How do unset variables work?) for tri-state semantics. See Q129 for safe access patterns with missing keys. See Q260 for Dict key type requirements and custom keys.\n\nUse 'ek9 -h Dict' to see the full API.","ek9Example":"defines module qa.dict.operations\n\n  defines program\n    DictOperationsDemo()\n      stdout <- Stdout()\n\n      ages <- {\"Alice\": 30, \"Bob\": 25, \"Charlie\": 35}\n\n      // === CONTAINS (KEY CHECK) ===\n\n      hasAlice <- ages contains \"Alice\"\n      stdout.println(`Contains Alice: ${hasAlice}`)\n\n      if \"Bob\" is in ages\n        stdout.println(\"Found Bob\")\n\n      if \"Zara\" is not in ages\n        stdout.println(\"Zara not found\")\n\n      // === KEYS AND VALUES ITERATORS ===\n\n      keyIter <- ages.keys()\n      while keyIter?\n        stdout.println(`Key: ${keyIter.next()}`)\n\n      valIter <- ages.values()\n      while valIter?\n        stdout.println(`Value: ${valIter.next()}`)\n\n      // === MERGING DICTS ===\n\n      d1 <- {\"x\": 1, \"y\": 2}\n      d2 <- {\"y\": 99, \"z\": 3}\n\n      // + creates a new merged dict\n      merged <- d1 + d2\n      stdout.println(`Merged: ${merged}`)\n\n      // :~: merge operator mutates left-hand side\n      d1 :~: d2\n      stdout.println(`After merge: ${d1}`)\n\n      // === COPY ===\n\n      copied <- Dict() of (String, Integer)\n      copied :=: ages\n      stdout.println(`Copied: ${copied}`)\n\n      // === COMPARISON ===\n\n      c1 <- {\"a\": 1, \"b\": 2}\n      c2 <- {\"a\": 1, \"b\": 2}\n      c3 <- {\"x\": 9}\n\n      require c1 == c2\n      require c1 <> c3\n      stdout.println(`c1 == c2: ${c1 == c2}`)\n      stdout.println(`c1 <> c3: ${c1 <> c3}`)\n\n      // === STRING AND JSON CONVERSION ===\n\n      asString <- $ages\n      stdout.println(`As string: ${asString}`)\n      stdout.println(`As JSON: ${$ ages}`)\n\n      // === HASHCODE ===\n\n      hash <- #? ages\n      stdout.println(`Hash: ${hash}`)\n\n      // === EMPTY DICT IS SET ===\n\n      emptyDict <- Dict() of (String, Integer)\n      require emptyDict?\n      require emptyDict is empty\n      stdout.println(`Empty dict isSet: ${emptyDict?}`)\n      stdout.println(`Empty dict isEmpty: ${emptyDict is empty}`)","migrationContext":"Java: HashMap .containsKey(), .keySet(), .values(), .putAll() for merge, no operator overloading. Python: 'in' operator, .keys(), .values(), dict union | (3.9+), == comparison. JavaScript: Object.keys(), Object.values(), spread for merge, no == comparison. Rust: .contains_key(), .keys(), .values(), .extend() for merge. Go: comma-ok idiom, no built-in merge or comparison. Kotlin: 'in' operator, .keys, .values, + for merge. EK9: 'contains'/'is in'/'is not in', .keys()/.values() iterators, + for new merged dict, :~: for in-place merge, :=: deep copy, == comparison, $$ for JSON.","keywords":["beginner","compare","contains","copy","dict","empty","first","hashcode","intro","iterator","json","keys","merge","operations","set","start","values"],"primaryTopics":["dict operations","dictionary methods","dict API"],"typicalErrors":[{"error":"E50060","correct":"hasAlice <- ages contains \"Alice\"","incorrect":"hasAlice <- ages.get(\"Alice\")","explanation":"Dict has no get() method in EK9. Use the contains operator for key existence checks, or getOrDefault() for value retrieval with a fallback. See ek9 -h E50060 for details."}],"companions":[]}
{"id":91,"category":"Getting Started","question":"What advanced features do Integer and Float support?","url":"https://ek9.io/qa/QA0091.html","alternatePhrasings":["How do mod, rem, and bitwise operators work on Integer in EK9?","How do I format numbers with locale-specific separators in EK9?","What happens when I mix Float and Integer in an expression?"],"answer":"Beyond basic arithmetic (see Q39), EK9 Integer and Float support mod/rem, factorial, bitwise logic, increment/decrement, mixed-type operations, unset propagation, locale formatting, and stream pipelines.\n\nMOD VS REM\nBoth give the remainder after division, but differ for negative numbers. mod always returns a non-negative result (mathematical modulo). rem preserves the sign of the dividend (like Java %).\n  17 mod 5 = 2,    17 rem 5 = 2      (identical for positives)\n  -17 mod 5 = 3,   -17 rem 5 = -2    (differ for negatives)\nmod is useful for wrapping (clock arithmetic, array indexing). rem matches C/Java % behaviour.\n\nFACTORIAL\nThe ! operator computes factorial:\n  five <- 5\n  five! returns 120 (5 * 4 * 3 * 2 * 1)\n\nBITWISE: AND, OR, XOR\nInteger supports bitwise logic operators:\n  0b11001100 and 0b10101010 = 0b10001000\n  0b11001100 or 0b10101010 = 0b11101110\n  0b11001100 xor 0b10101010 = 0b01100110\nIMPORTANT: Integer does NOT have shift operators. Use the Bits type for shifting, rotation, and variable-width bit sequences. Convert with Bits(integerValue).\n\nINCREMENT AND DECREMENT\nBoth Integer and Float support ++ and --:\n  counter <- 10\n  counter++ gives 11,  counter-- gives 10\n  fVal <- 1.5\n  fVal++ gives 2.5,  fVal-- gives 1.5\n\nMIXED FLOAT AND INTEGER OPERATIONS\nFloat operators accept both Float and Integer arguments:\n  3.5 * 4 gives Float 14.0,  3.5 + 4 gives Float 7.5\nInteger operators only accept Integer. To convert Integer to Float, use #^ promote (see Q25).\n\nUNSET PROPAGATION\nOperations involving unset values return unset:\n  unsetInt <- Integer()\n  unsetInt + 1 is unset\n  unsetFloat <- Float()\n  unsetFloat + 1.0 is unset\nThis follows the tri-state principle (see Q29): operations on unknown values produce unknown results.\n\nLOCALE FORMATTING\nBoth types format through the Locale type:\n  Locale(\"en_GB\").format(1234567) gives \"1,234,567\"\n  Locale(\"de_DE\").format(1234567) gives \"1.234.567\"\nFloat adds decimal place control:\n  Locale(\"en_GB\").format(3.14159, 2) gives \"3.14\"\n  Locale(\"de_DE\").format(3.14159, 4) gives \"3,1416\"\nSee Q44 for the full Locale API.\n\nFOR-RANGE WITH STEP AND STREAM PIPELINE\nInteger integrates with for-range and stream pipelines:\n  theValues <- List() of Integer\n  intSum <- for i in 1 ... 11 by 2 | tee in theValues | collect as Integer\nGenerates 1, 3, 5, 7, 9, 11 (step by 2), tees each into theValues, collects the sum (36).\n\nSee Q39 for Integer and Float basics (literals, arithmetic, division by zero, promote). See Q40 for the Bits type. See Q44 for full Locale formatting. See Q240 for arithmetic and mathematical operators (mod, rem, abs, sqrt, ^).","ek9Example":"defines module qa.numeric.advanced\n\n  defines program\n    NumericAdvancedDemo()\n      stdout <- Stdout()\n\n      // === MOD VS REM ===\n\n      stdout.println(`17 mod 5 = ${17 mod 5}`)\n      stdout.println(`17 rem 5 = ${17 rem 5}`)\n      stdout.println(`-17 mod 5 = ${-17 mod 5}`)\n      stdout.println(`-17 rem 5 = ${-17 rem 5}`)\n\n      // === FACTORIAL ===\n\n      five <- 5\n      stdout.println(`5! = ${five!}`)\n\n      // === BITWISE LOGIC ===\n\n      x <- 0b11001100\n      y <- 0b10101010\n\n      stdout.println(`x and y: ${x and y}`)\n      stdout.println(`x or y: ${x or y}`)\n      stdout.println(`x xor y: ${x xor y}`)\n\n      // For shift operations, use the Bits type\n      bitValue <- Bits(x)\n      stdout.println(`Bits of x: ${bitValue}`)\n\n      // === INCREMENT AND DECREMENT ===\n\n      counter <- 10\n      counter++\n      stdout.println(`After ++: ${counter}`)\n      counter--\n      stdout.println(`After --: ${counter}`)\n\n      fVal <- 1.5\n      fVal++\n      stdout.println(`Float after ++: ${fVal}`)\n      fVal--\n      stdout.println(`Float after --: ${fVal}`)\n\n      // === MIXED FLOAT AND INTEGER ===\n\n      product <- 3.5 * 4\n      sum <- 3.5 + 4\n      stdout.println(`3.5 * 4 = ${product}`)\n      stdout.println(`3.5 + 4 = ${sum}`)\n\n      // === UNSET PROPAGATION ===\n\n      unsetInt <- Integer()\n      propagatedInt <- unsetInt + 1\n      stdout.println(`Unset int + 1 isSet: ${propagatedInt?}`)\n\n      unsetFloat <- Float()\n      propagatedFloat <- unsetFloat + 1.0\n      stdout.println(`Unset float + 1.0 isSet: ${propagatedFloat?}`)\n\n      // === LOCALE FORMATTING ===\n\n      enGB <- Locale(\"en_GB\")\n      deutsch <- Locale(\"de_DE\")\n      largeNum <- 1234567\n\n      stdout.println(`GB: ${enGB.format(largeNum)}`)\n      stdout.println(`DE: ${deutsch.format(largeNum)}`)\n\n      pi <- 3.141592653589793\n      stdout.println(`GB 2 decimals: ${enGB.format(pi, 2)}`)\n      stdout.println(`DE 4 decimals: ${deutsch.format(pi, 4)}`)\n\n      // === FOR-RANGE WITH STEP AND STREAM PIPELINE ===\n\n      theValues <- List() of Integer\n\n      intSum <- for i in 1 ... 11 by 2 | tee in theValues | collect as Integer\n      stdout.println(`Values: ${theValues}`)\n      stdout.println(`Sum: ${intSum}`)","migrationContext":"Java: % operator (rem semantics only), no factorial operator, bitwise &/|/^/<</>>, Integer.parseInt for locale-agnostic formatting, NumberFormat for locale display, IntStream for range pipelines. Python: % (mod semantics), no factorial operator (use math.factorial), bitwise &/|/^/<</>>, locale module. JavaScript: % (rem semantics), no factorial, bitwise &/|/^/<</>>, Intl.NumberFormat for locale. Rust: % (rem), no factorial, bitwise &/|/^/<</>>, no built-in locale formatting. Go: % (rem), no factorial, bitwise &/|/^/<</>>, no built-in locale formatting. EK9: both mod and rem operators, ! factorial, and/or/xor bitwise (shift on Bits type only), Locale.format() built-in, for-range with stream pipeline integration.","keywords":["advanced","and","beginner","bitwise","decrement","factorial","first","format","increment","intro","locale","migrate","mixed","mod","or","pipeline","propagation","rem","remainder","start","stream","unset","xor"],"primaryTopics":[],"typicalErrors":[{"error":"E50001","correct":"bitValue <- Bits(x)","incorrect":"shifted <- x << 2","explanation":"Integer does NOT have shift operators in EK9. Use the Bits type for shifting, rotation, and variable-width bit sequences. Convert with Bits(integerValue). See ek9 -h E50001 for details."}],"companions":[]}
{"id":92,"category":"Getting Started","question":"How does DateTime work with timezones in EK9?","url":"https://ek9.io/qa/QA0092.html","alternatePhrasings":["How do I use DateTime literals with UTC and offsets?","How does EK9 handle timezone conversion?","What is the difference between Date and DateTime in EK9?"],"answer":"DateTime combines date, time, and timezone into a single built-in type. It is the preferred type for timestamps, events, logging, and any data that needs timezone awareness.\n\nLITERAL SYNTAX\nUTC: 2024-06-15T10:30:00Z (the Z means UTC)\nWith offset: 2020-10-04T12:15:00-05:00 (five hours behind UTC)\n\nCONSTRUCTOR FORMS\nDateTime(2024, 06, 15, 10, 30) creates a DateTime with year, month, day, hour, minute. Additional constructors accept 3 args (date only), 4 args (with hour), or 6 args (with seconds).\nDateTime() creates an unset DateTime (not now). DateTime().today() returns the current date and time. DateTime().now() also returns the current date and time.\n\nACCESSORS\nAll Date accessors: year(), month(), day(), dayOfMonth(), dayOfWeek(), dayOfYear()\nTime accessors: hour(), minute(), second()\nTimezone: zone() returns the timezone string (e.g. 'Z' or '-05:00'). offSetFromUTC() returns the offset as a Duration (e.g. PT-5H for the -05:00 timezone).\nExtract parts: date() returns the Date portion, time() returns the Time portion.\n\nARITHMETIC\nDateTime + Duration gives DateTime. DateTime - Duration gives DateTime. DateTime - DateTime gives Duration. Compound assignment with += and -= also works.\n\nDATE PROMOTES TO DATETIME\nThe #^ promote operator converts Date to DateTime automatically. You can assign a Date to a DateTime variable directly.\n\nStream collection: cat durations | collect as DateTime (adds durations from epoch 1970-01-01T00:00:00Z).\n\nSee Q31 for Date and Time basics. See Q32 for Duration details. See Q44 for locale formatting with shortFormat(), longFormat(), and other locale-aware display methods.\n\nUse 'ek9 -h DateTime' to see the full API.\n\nSee Q31 for date and time. See Q32 for duration. See Q541-Q553 for deep-dive timezone coverage including withSameInstant vs withZone, UTC storage, and cross-timezone comparison.","ek9Example":"defines module qa.datetime\n\n  defines program\n    DateTimeDemo()\n      stdout <- Stdout()\n\n      // DateTime literals with timezone\n      meetingUTC <- 2024-06-15T10:30:00Z\n      meetingEST <- 2020-10-04T12:15:00-05:00\n      stdout.println(`UTC meeting: ${meetingUTC}`)\n      stdout.println(`EST meeting: ${meetingEST}`)\n\n      // Constructor form\n      constructed <- DateTime(year: 2024, month: 06, dayOfMonth: 15, hour: 10, minute: 30)\n      stdout.println(`Constructed matches UTC: ${constructed == meetingUTC}`)\n\n      // DateTime() is unset, DateTime().today() gets current\n      unsetDT <- DateTime()\n      stdout.println(`Unset isSet: ${unsetDT?}`)\n      currentDT <- DateTime().today()\n      stdout.println(`Current: ${currentDT}`)\n\n      // All accessors\n      stdout.println(`Year: ${meetingUTC.year()}, Month: ${meetingUTC.month()}, Day: ${meetingUTC.day()}`)\n      stdout.println(`Hour: ${meetingUTC.hour()}, Minute: ${meetingUTC.minute()}, Second: ${meetingUTC.second()}`)\n      stdout.println(`Day of week: ${meetingUTC.dayOfWeek()}, Day of year: ${meetingUTC.dayOfYear()}`)\n\n      // Timezone accessors\n      tz <- meetingEST.zone()\n      offset <- meetingEST.offSetFromUTC()\n      stdout.println(`Timezone: ${tz}, Offset: ${offset}`)\n\n      // Extract date and time parts\n      datePart <- meetingUTC.date()\n      timePart <- meetingUTC.time()\n      stdout.println(`Date part: ${datePart}, Time part: ${timePart}`)\n\n      // DateTime + Duration arithmetic\n      meetingEnd <- meetingUTC + PT1H30M\n      prepTime <- meetingUTC - PT30M\n      stdout.println(`Meeting ends: ${meetingEnd}`)\n      stdout.println(`Prep starts: ${prepTime}`)\n\n      // DateTime - DateTime gives Duration\n      laterMeeting <- 2024-06-15T14:00:00Z\n      meetingGap <- laterMeeting - meetingUTC\n      stdout.println(`Gap between meetings: ${meetingGap}`)\n\n      // Compound assignment\n      scheduled <- 2024-06-15T09:00:00Z\n      scheduled += P1D\n      stdout.println(`Rescheduled: ${scheduled}`)\n\n      // Date promotes to DateTime\n      birthday <- 1971-02-01\n      birthdayDT as DateTime: birthday\n      stdout.println(`Promoted: ${birthdayDT}`)\n\n      // Stream collection from durations\n      durations as List of Duration := [P1Y1M4D, PT2H30M]\n      collectedDT <- cat durations | collect as DateTime\n      stdout.println(`Collected: ${collectedDT}`)\n\n      // Locale formatting\n      enGB <- Locale(\"en_GB\")\n      stdout.println(`Short: ${enGB.shortFormat(meetingUTC)}`)\n      stdout.println(`Long: ${enGB.longFormat(meetingUTC)}`)","migrationContext":"Java: java.time.ZonedDateTime/OffsetDateTime (Java 8), verbose factory methods, no literals. Python: datetime.datetime with pytz/zoneinfo for timezones, no literals. Rust: no built-in, chrono crate with DateTime<Tz>. Go: time.Time with time.Location, no literals, bizarre reference format. JavaScript: Date has no timezone support beyond local/UTC, Temporal.ZonedDateTime still not finalised. C#: DateTimeOffset reasonable but no literals. EK9: DateTime built-in with literal syntax including timezone offset, zone()/offSetFromUTC() accessors, Duration arithmetic, Date promotion, no imports needed.","keywords":["beginner","combined","date","datetime","first","intro","offset","promote","start","time","timestamp","timezone","utc","zone"],"primaryTopics":[],"typicalErrors":[{"error":"E50060","correct":"meetingEST.zone()","incorrect":"meetingEST.getZone()","explanation":"EK9 DateTime uses short method names: zone(), date(), time() not getZone(), getDate(), getTime(). Triggers E50060 — method not resolved. See ek9 -h DateTime for the full API."},{"error":"E50060","correct":"tz <- meetingEST.zone()","incorrect":"tz <- meetingEST.getZone()","explanation":"EK9 DateTime uses short method names: zone(), date(), time() — not Java-style getZone(), getDate(), getTime(). Using a Java-style name triggers E50060 — method not resolved. Use 'ek9 -h DateTime' to see the full API."}],"companions":[]}
{"id":93,"category":"Classes and OOP","question":"How do I define a class in EK9?","url":"https://ek9.io/qa/QA0093.html","alternatePhrasings":["What is the syntax for creating a class in EK9?","How do classes work in EK9?","What does a basic EK9 class look like?","How do I create a class in EK9?"],"answer":"Classes in EK9 are defined under a 'defines class' section. They use indentation-based syntax with no braces, no semicolons, and no explicit visibility keywords like public or private. Properties are private by default, and methods use '->' for parameters and '<-' for return values.\n\nBASIC CLASS\nDefine a class with properties and methods:\n  defines class\n    Person\n      name <- String()\n      age <- Integer()\nProperties use the declaration operator '<-' for type-inferred fields or explicit types.\n\nPROPERTIES\nProperties can be declared with type inference or explicit types:\n  name <- \"Steve\"                type inferred as String\n  age as Integer: 25             explicit type with initial value\n  email <- String()              type inferred, starts unset\n\nMETHODS\nMethods use '->' for incoming parameters and '<-' for the return:\n  greet()\n    -> greeting as String\n    <- rtn as String: `${greeting} ${name}`\nMethods are public by default. Use 'private' or 'protected' to restrict access.\n\nCONSTRUCTOR\nConstructors share the class name:\n  Person()\n    -> name as String\n    this.name: name\n\nDEFAULT OPERATOR\nThe 'default operator' keyword generates standard operators (==, <>, <=>, $, #?, ?) based on fields:\n  default operator\n\nDYNAMIC CLASSES\nEK9 also supports dynamic classes defined inline. Two forms exist.\n\nUnnamed dynamic class implements a trait inline with variable capture:\n  handler <- (msg) with trait of Greeter as class\n    override greetPerson()\n      <- rtn as String: msg\n    default operator ?\nCaptures variables from enclosing scope. Must override all abstract methods and include 'default operator ?' when capturing.\n\nNamed dynamic class creates a reusable type elevated to module scope:\n  record <- PersonRecord(name: n, year: y) as class\n    describe() as pure\n      <- rtn as String: combine name and year\n    default operator ?\nNamed dynamic classes can be used as types in function signatures and collections.\n\nSee Q22 for variable declarations. See Q49 for functions. See Q50 for function returns. See Q94 for constructors. See Q95 for field visibility. See Q96 for operators. See Q97 for records vs classes. See Q238 for the fixed operator set and enforcement rules. See Q245 for a complete example of implementing all operators on a custom type. See Q115 for dynamic classes in detail. See Q52 for dynamic functions. See Q596 for function vs method distinction.","ek9Example":"defines module qa.oop.defineclass\n\n  defines trait\n\n    Greeter\n      greetPerson() as abstract\n        <- rtn as String?\n\n  defines class\n\n    Person\n      name <- String()\n      age <- Integer()\n\n      Person()\n        ->\n          name as String\n          age as Integer\n        this.name: name\n        this.age: age\n\n      greet()\n        -> greeting as String\n        <- rtn as String: `${greeting} ${name}`\n\n      default operator\n\n  defines program\n\n    DefineClassDemo()\n      stdout <- Stdout()\n\n      // === BASIC CLASS ===\n\n      person <- Person(\"Steve\", 30)\n      stdout.println(`Person: ${person}`)\n\n      // === METHODS ===\n\n      message <- person.greet(\"Hello\")\n      stdout.println(message)\n\n      // === DEFAULT OPERATOR ===\n\n      same <- Person(\"Steve\", 30)\n      stdout.println(`Equal: ${person == same}`)\n      stdout.println(`Compare: ${person <=> same}`)\n\n      // === CONSTRUCTOR ===\n\n      another <- Person(\"Jane\", 25)\n      stdout.println(`Different: ${person <> another}`)\n\n      // === UNNAMED DYNAMIC CLASS ===\n\n      msg <- \"Hi there\"\n      handler <- (msg) with trait of Greeter as class\n        override greetPerson()\n          <- rtn as String: msg\n        default operator ?\n\n      stdout.println(handler.greetPerson())\n\n      // === NAMED DYNAMIC CLASS ===\n\n      firstName <- \"Carol\"\n      birthYear <- 1990\n      entry <- PersonRecord(firstName, birthYear) as class\n        describe() as pure\n          <- rtn as String: `${firstName} born ${birthYear}`\n        operator $ as pure\n          <- rtn as String: describe()\n        default operator ?\n\n      stdout.println($entry)","migrationContext":"Java: class MyClass { private String name; public MyClass(String name) { this.name = name; } } with braces, semicolons, explicit visibility. Python: class MyClass: with __init__(self, name) and self.name, dynamic typing. Rust: struct + impl blocks, no inheritance, traits for behavior. Go: struct types with methods via receiver functions, no classes. Kotlin: class MyClass(val name: String) with concise primary constructor. Swift: class MyClass with init() initializer, properties with let/var, deinit for cleanup, final by default. EK9: indentation-based, no braces or semicolons, properties private by default, constructors named after class, default operator generates standard operators.","keywords":["basic","class","constructor","create","default","define","indentation","method","new","object","object-oriented","oop","operator","property","swift","syntax"],"primaryTopics":["class","define class","create class","object oriented"],"typicalErrors":[{"error":"E06180","correct":"person.greet(\"Hello\")","incorrect":"person.name","explanation":"Class properties are always private in EK9. You cannot access 'name' directly from outside the class; define an accessor method instead. See ek9 -h E06180 for details."},{"error":"E08180","correct":"name <- String()","incorrect":"name as String","explanation":"Class properties must be initialized inline or marked for injection with '!'. A bare 'name as String' with no initializer triggers E08180. Use '<-' with a default value, explicit type with ':' initializer, or 'as String?' for an unset field. See ek9 -h E08180 for details."},{"error":"E07140","correct":"handler <- (msg) with trait of Greeter as class\n        override greetPerson()\n          <- rtn as String: msg\n        default operator ?","incorrect":"handler <- (msg) with trait of Greeter as class\n        default operator ?","explanation":"A dynamic class implementing a trait must override ALL abstract methods. Omitting greetPerson() means the abstract method is unimplemented. See ek9 -h E07140 for details."},{"error":"E07236","correct":"handler <- (msg) with trait of Greeter as class\n        override greetPerson()\n          <- rtn as String: msg\n        default operator ?","incorrect":"handler <- (msg) with trait of Greeter as class\n        override greetPerson()\n          <- rtn as String: msg","explanation":"Dynamic classes with captured variables MUST include 'default operator ?' because captures become private fields. Without it the compiler cannot determine the set/unset state. See ek9 -h E07236 for details."}],"companions":[],"oracleToolHint":{"tool":"ek9_scaffold","intent":"class","description":"Oracle can generate a class outline with fields, constructor, methods, and default operator declarations."}}
{"id":94,"category":"Classes and OOP","question":"How do I implement a constructor in EK9?","url":"https://ek9.io/qa/QA0094.html","alternatePhrasings":["What is the syntax for EK9 class constructors?","How do constructors work in EK9 classes?","Can I have multiple constructors in an EK9 class?"],"answer":"Constructors in EK9 share the name of the class. You can define multiple constructors with different parameter lists. Properties are initialised using the colon assignment operator.\n\nBASIC CONSTRUCTOR\nA constructor takes parameters with '->' and assigns to properties:\n  Account()\n    -> holder as String\n    this.holder: holder\nThe 'this.' prefix disambiguates property from parameter when names match.\n\nPROPERTY INITIALIZATION\nProperties can have default values or be set in the constructor:\n  balance <- 0.0              default value at declaration\n  holder <- String()          starts unset, must be set in constructor\n\nMULTIPLE CONSTRUCTORS\nOverload constructors with different parameter lists:\n  Account()\n    -> holder as String\n    this(holder, 0.0)\n  Account()\n    ->\n      holder as String\n      initialBalance as Float\n    this.holder: holder\n    this.balance: initialBalance\nMultiple parameters use multi-line '->' blocks (comma-separated on one line is not valid). Use 'this(...)' to chain to another constructor.\n\nCOPY CONSTRUCTOR\nThe copy operator ':=:' copies all fields from another instance:\n  operator :=:\n    -> from as Account\n    holder :=: from.holder\n    balance :=: from.balance\n\nDEFAULT CONSTRUCTOR\nUse 'default' keyword to hide or control the no-arg constructor:\n  default private Account()\nThis prevents external creation without parameters.\n\nPURE CONSTRUCTORS\nMark a constructor 'as pure' to enforce immutability rules. Pure constructors use ':=?' (guarded assign) instead of ':' for field assignment. Critical rule: if ANY constructor is pure, ALL constructors must be pure (E05190).\n\nACCESS MODIFIERS\nConstructors are public by default. Do NOT write 'public Account()' — the compiler rejects redundant 'public' (E07280). Use 'private' or 'protected' only when restricting access.\n\nDYNAMIC CLASS CONSTRUCTORS\nNamed dynamic classes can define explicit constructors for reusable types:\n  entry <- PersonRecord(name: n, age: a) as class\n    PersonRecord()\n      ->\n        name as String\n        age as Integer\n      this.name: name\n      this.age: age\n    default operator ?\nThe captured variables become fields, and custom constructors control the interface.\n\nSee Q93 for class basics. See Q95 for field visibility. See Q104 for uninitialised properties. See Q98 for record operators. See Q118 for builder pattern. See Q580 for constructor delegation patterns. See Q563 for pure constructor assignment rules. See Q115 for dynamic classes. See Q598 for why EK9 has no default parameters and uses constructor overloading instead.","ek9Example":"defines module qa.oop.constructors\n\n  defines class\n\n    Account\n      holder <- String()\n      balance <- 0.0\n\n      default private Account()\n\n      Account()\n        -> holder as String\n        this(holder, 0.0)\n\n      Account()\n        ->\n          holder as String\n          initialBalance as Float\n        this.holder: holder\n        this.balance: initialBalance\n\n      holder() as pure\n        <- rtn as String: holder\n\n      balance() as pure\n        <- rtn as Float: balance\n\n      deposit()\n        -> amount as Float\n        balance += amount\n\n      operator :=:\n        -> from as Account\n        holder :=: from.holder\n        balance :=: from.balance\n\n      default operator ?\n\n  defines program\n\n    ConstructorDemo()\n      stdout <- Stdout()\n\n      // === BASIC CONSTRUCTOR ===\n\n      account1 <- Account(\"Alice\")\n      stdout.println(`Holder: ${account1.holder()}, Balance: ${account1.balance()}`)\n\n      // === MULTIPLE CONSTRUCTORS ===\n\n      account2 <- Account(\"Bob\", 100.0)\n      stdout.println(`Holder: ${account2.holder()}, Balance: ${account2.balance()}`)\n\n      // === CONSTRUCTOR CHAINING ===\n\n      account1.deposit(50.0)\n      stdout.println(`After deposit: ${account1.balance()}`)\n\n      // === COPY OPERATOR ===\n\n      account3 <- Account(\"Temp\")\n      account3 :=: account2\n      stdout.println(`Copied: ${account3.holder()}, Balance: ${account3.balance()}`)","migrationContext":"Java: constructors match class name, this() chaining, no named parameters, copy via clone() or copy constructor. Python: __init__(self) only, no overloading (use defaults), copy via copy.deepcopy(). Rust: no constructors, use associated functions like new(), From trait for conversion. Go: no constructors, convention is NewType() factory functions. Kotlin: primary constructor in class header, secondary with constructor keyword, copy() on data classes. EK9: constructors match class name, multiple overloads, this() chaining, ':=:' copy operator, default private constructors.","keywords":["chain","constant","constructor","copy","default","initialise","object-oriented","overload","parameter","private","property","this"],"primaryTopics":["constructor","class constructor"],"typicalErrors":[{"error":"E08180","correct":"holder <- String()","incorrect":"holder as String","explanation":"A bare 'holder as String' with no initializer and no injection marker '!' triggers E08180. Use '<-' with a default value, explicit type with ':' initializer, or 'as String?' for an unset field. See ek9 -h E08180 for details."},{"error":"E07280","correct":"Account()\n        -> holder as String","incorrect":"public Account()\n        -> holder as String","explanation":"Constructors and methods are public by default in EK9. Adding an explicit 'public' modifier is redundant and triggers E07280. Simply omit it. Use 'private' or 'protected' only when restricting access. See ek9 -h E07280 for details."},{"error":"E08130","correct":"Account()\n        -> holder as String\n        this(holder, 0.0)","incorrect":"Account() as pure\n        -> holder as String\n        this(holder, 0.0)","explanation":"If ANY constructor is marked 'as pure', ALL constructors must be pure. Mixing pure and non-pure constructors triggers E05190. Also note pure constructors use ':=?' not ':' for field assignment. See ek9 -h E05190 for details."}],"companions":[],"oracleToolHint":{"tool":"ek9_scaffold","intent":"class","description":"Oracle can generate a class with constructor patterns including delegation and field initialisation."}}
{"id":95,"category":"Classes and OOP","question":"How do I control field visibility in EK9?","url":"https://ek9.io/qa/QA0095.html","alternatePhrasings":["Are class properties public or private by default in EK9?","How does encapsulation work in EK9?","What is the difference between class, record, and component field visibility?"],"answer":"EK9 fields have fixed visibility rules that vary by construct type. There are no access modifiers on fields; the construct type determines visibility.\n\nCLASSES: ALWAYS PRIVATE\nClass properties are always private. Access them through explicit accessor methods:\n  defines class\n    Person\n      name <- String()\n      name() as pure\n        <- rtn as String: name\nYou write accessor methods to expose properties. There is no getter/setter annotation.\n\nRECORDS: ALWAYS PUBLIC\nRecord properties are always public. Access them directly:\n  defines record\n    Point\n      x <- 0.0\n      y <- 0.0\nCallers use 'point.x' directly. Records are data carriers by design.\n\nCOMPONENTS: ALWAYS PRIVATE\nComponent properties are always private, just like classes. Components are large-scale containers for dependency injection:\n  defines component\n    Processor as abstract\n      process()\n        -> input as String\n        <- rtn as String\nConcrete components access their own fields internally.\n\nTRAITS: NO FIELDS\nTraits cannot have properties at all. They define method contracts with no retained state:\n  defines trait\n    Printable\n      show() as pure abstract\n        <- rtn as String\n\nNO ACCESS MODIFIERS ON FIELDS\nUnlike methods (which can be public, protected, or private), fields have no access modifier in the grammar. Writing 'private name <- String()' or 'public x <- 0.0' causes a parse error. The construct type alone determines field visibility.\n\nFIELDS MUST BE INITIALISED\nAll fields must be initialised at declaration, set in a constructor, or marked for injection with '!' on components. Uninitialised fields cause E08180.\n\nSee Q93 for class basics. See Q94 for constructors. See Q97 for records vs classes. See Q106 for traits. See Q111 for components. See Q573 for method access modifier rules.","ek9Example":"defines module qa.oop.visibility\n\n  defines trait\n\n    Printable\n      show() as pure abstract\n        <- rtn as String?\n\n  defines class\n\n    Person with trait of Printable\n      name <- String()\n      age <- Integer()\n\n      Person()\n        ->\n          name as String\n          age as Integer\n        this.name: name\n        this.age: age\n\n      name() as pure\n        <- rtn as String: name\n\n      age() as pure\n        <- rtn as Integer: age\n\n      override show() as pure\n        <- rtn as String: `Person: ${name}, age ${age}`\n\n      default operator\n\n  defines record\n\n    Point\n      x <- 0.0\n      y <- 0.0\n\n      Point()\n        ->\n          x as Float\n          y as Float\n        this.x: x\n        this.y: y\n\n      default operator\n\n  defines component\n\n    Processor as abstract\n\n      process() abstract\n        -> input as String\n        <- rtn as String?\n\n      default operator ?\n\n  defines component\n\n    UpperCaseProcessor extends Processor\n      prefix <- \"PROCESSED\"\n\n      override process()\n        -> input as String\n        <- rtn as String: `${prefix}: ${input.upperCase()}`\n\n      default operator ?\n\n  defines program\n\n    VisibilityDemo()\n      stdout <- Stdout()\n\n      // === CLASS FIELDS: always private, use accessor methods ===\n\n      person <- Person(\"Alice\", 30)\n      stdout.println(`Name via accessor: ${person.name()}`)\n      stdout.println(`Age via accessor: ${person.age()}`)\n      stdout.println(person.show())\n\n      // === RECORD FIELDS: always public, access directly ===\n\n      point <- Point(3.0, 4.0)\n      stdout.println(`Direct access x: ${point.x}, y: ${point.y}`)\n\n      // === COMPONENT FIELDS: always private, used internally ===\n\n      processor <- UpperCaseProcessor()\n      stdout.println(processor.process(\"hello world\"))\n\n      // === TRAIT: no fields, just method contracts ===\n\n      printable as Printable: person\n      stdout.println(`Via trait: ${printable.show()}`)\n\n      // === COMPARISON ===\n\n      point2 <- Point(3.0, 4.0)\n      stdout.println(`Points equal: ${point == point2}`)\n\n      person2 <- Person(\"Alice\", 30)\n      stdout.println(`Persons equal: ${person == person2}`)","migrationContext":"Java: fields private by convention with explicit getters/setters, no enforcement unless you add private. Python: no true private, underscore convention, properties via @property decorator. Rust: struct fields private by default outside module, pub for public. Go: uppercase for exported, lowercase for unexported. Kotlin: val/var properties with automatic getters/setters, backing field access. C#: fields private by default, properties with get/set accessors. EK9: class and component properties always private (no modifier allowed), record properties always public, traits have no fields. The construct type determines visibility with no override possible.","keywords":["E08180","accessor","class","component","encapsulation","field","getter","object-oriented","private","property","public","record","setter","trait","visibility"],"primaryTopics":["field visibility","access modifier","public private","encapsulation"],"typicalErrors":[{"error":"E06180","correct":"person.name()","incorrect":"person.name","explanation":"Class fields are always private in EK9. There is no 'private' keyword because it is the only option. Access fields through accessor methods like 'name()'. See ek9 -h E06180 for details."},{"error":"E50060","correct":"person.name()","incorrect":"person.getName()","explanation":"EK9 does not use Java-style 'getX()' accessors. The accessor method matches the field name: 'name()' not 'getName()'. See ek9 -h E50060 for details."},{"error":"E06180","correct":"processor.process(\"hello world\")","incorrect":"processor.prefix","explanation":"Component fields are always private. Even though the field 'prefix' exists on UpperCaseProcessor, it cannot be accessed from outside the component. Use the public methods instead. See ek9 -h E06180 for details."}],"companions":[]}
{"id":96,"category":"Classes and OOP","question":"How do operators work in EK9?","url":"https://ek9.io/qa/QA0096.html","alternatePhrasings":["How do I define custom operators on an EK9 type?","What constructs support operators in EK9?","How does operator overloading work in EK9?","How do I compare objects in EK9?","Which EK9 constructs can have operators?"],"answer":"EK9 supports operators on records, classes, components, traits, and dynamic classes with uniform syntax. 'default operator' auto-generates standard operators from fields.\n\nKEY CATEGORIES\nComparison: ==, <>, <=>, <, >, <=, >=. Conversion: $ (String), $$ (JSON), #? (hashcode), #^ (promote). IsSet: operator ? (MUST define on aggregates with fields, E07235). Mutation: :=: (copy), :~: (merge), :^: (replace).\n\n'default operator' generates all standard operators from fields. Cannot be used on traits (E07030). Child classes call parent via $super, super?, super :=: source.\n\nSee Q93 for class basics. See Q97 for records. See Q106 for traits. See Q238 for the complete operator set. See Q241 for mutation operators.","ek9Example":"defines module qa.oop.operators\n\n  defines trait\n\n    Measurable\n      measure() as pure abstract\n        <- rtn as Float?\n\n      operator <=> as pure\n        -> other as Measurable\n        <- rtn as Integer: measure() <=> other.measure()\n\n      operator == as pure\n        -> other as Measurable\n        <- rtn as Boolean: measure() == other.measure()\n\n      operator <> as pure\n        -> other as Measurable\n        <- rtn as Boolean: not (this == other)\n\n      override operator ? as pure\n        <- rtn as Boolean: measure()?\n\n  defines class\n\n    Temperature with trait of Measurable as open\n      degrees <- Float()\n      scale <- String()\n\n      default private Temperature() as pure\n\n      Temperature() as pure\n        ->\n          degrees as Float\n          scale as String\n        this.degrees :=? degrees\n        this.scale :=? scale\n\n      override measure() as pure\n        <- rtn as Float: degrees\n\n      operator + as pure\n        -> other as Temperature\n        <- rtn as Temperature: Temperature(degrees + other.degrees, scale)\n\n      operator - as pure\n        -> other as Temperature\n        <- rtn as Temperature: Temperature(degrees - other.degrees, scale)\n\n      operator :=:\n        -> source as Temperature\n        degrees :=: source.degrees\n        scale :=: source.scale\n\n      operator $ as pure\n        <- rtn as String: `${$degrees}${scale}`\n\n      operator #? as pure\n        <- rtn as Integer: #?degrees\n\n      override operator ? as pure\n        <- rtn as Boolean: degrees? and scale?\n\n    PreciseTemperature extends Temperature\n      precision as Integer: Integer()\n\n      PreciseTemperature() as pure\n        ->\n          degrees as Float\n          scale as String\n          precision as Integer\n        super(degrees, scale)\n        this.precision :=? precision\n\n      override operator :=:\n        -> source as PreciseTemperature\n        super :=: source\n        precision :=: source.precision\n\n      override operator $ as pure\n        <- rtn as String: `${$super} (precision: ${$precision})`\n\n      override operator ? as pure\n        <- rtn as Boolean: super? and precision?\n\n  defines record\n\n    Point\n      x <- 0.0\n      y <- 0.0\n\n      Point()\n        ->\n          x as Float\n          y as Float\n        this.x: x\n        this.y: y\n\n      default operator\n\n  defines component\n\n    Gauge as abstract\n\n      reading() as pure abstract\n        <- rtn as Float?\n\n      operator $ as pure\n        <- rtn as String: `Gauge: ${$reading()}`\n\n      override operator ? as pure\n        <- rtn as Boolean: reading()?\n\n  defines component\n\n    TemperatureGauge extends Gauge\n      sensor as Temperature: Temperature(0.0, \"\")\n\n      TemperatureGauge()\n        -> sensor as Temperature\n        this.sensor: sensor\n\n      override reading() as pure\n        <- rtn as Float: sensor.measure()\n\n      default operator ?\n\n  defines program\n\n    OperatorsDemo()\n      stdout <- Stdout()\n\n      // === CLASS OPERATORS: custom implementations ===\n\n      t1 <- Temperature(20.0, \"C\")\n      t2 <- Temperature(5.0, \"C\")\n\n      sum <- t1 + t2\n      stdout.println(`Sum: ${sum}`)\n\n      diff <- t1 - t2\n      stdout.println(`Diff: ${diff}`)\n\n      stdout.println(`Equal: ${t1 == t2}`)\n      stdout.println(`Compare: ${t1 <=> t2}`)\n      stdout.println(`String: ${t1}`)\n      stdout.println(`IsSet: ${t1?}`)\n\n      // === SUPER OPERATORS: child calls parent ===\n\n      pt <- PreciseTemperature(20.0, \"C\", 3)\n      stdout.println(`Precise: ${pt}`)\n\n      copied <- PreciseTemperature(0.0, \"\", 0)\n      copied :=: pt\n      stdout.println(`Copied: ${copied}`)\n\n      // === RECORD OPERATORS: default operator auto-generates ===\n\n      p1 <- Point(3.0, 4.0)\n      p2 <- Point(3.0, 4.0)\n      p3 <- Point(1.0, 2.0)\n\n      stdout.println(`Points equal: ${p1 == p2}`)\n      stdout.println(`Points differ: ${p1 <> p3}`)\n      stdout.println(`Point string: ${p1}`)\n\n      // === TRAIT OPERATORS: concrete operators on trait ===\n\n      measurable as Measurable: t1\n      stdout.println(`Via trait ==: ${measurable == t2}`)\n      stdout.println(`Via trait isSet: ${measurable?}`)\n\n      // === COMPONENT OPERATORS: custom $ and ? ===\n\n      gauge <- TemperatureGauge(Temperature(22.5, \"C\"))\n      stdout.println(`${gauge}`)\n      stdout.println(`Gauge set: ${gauge?}`)","migrationContext":"Java: equals/hashCode/toString methods. Python: dunder methods. Rust: derive macros. EK9: operator keyword syntax, 'default operator' auto-generates from fields.","keywords":["E07235","abstract","class","compare","comparison","component","copy","custom","default","dynamic","equality","hashcode","isSet","isset","merge","mutation","object-oriented","operator","overload","override","pure","record","string","trait","tri-state"],"primaryTopics":["operator","operator overloading","custom operator"],"typicalErrors":[{"error":"E05120","correct":"override operator ? as pure","incorrect":"operator ? as pure","explanation":"The '?' operator is inherited from the base type. When providing a custom implementation, you must use the 'override' keyword. Omitting 'override' triggers E05120. See ek9 -h E05120 for details."},{"error":"E07500","correct":"operator == as pure","incorrect":"operator ==","explanation":"Comparison and query operators must be marked 'as pure'. Equality checks cannot have side effects. Omitting 'as pure' triggers E07500. See ek9 -h E07500 for details."},{"error":"E07235","correct":"default operator ?","incorrect":"//no operator ? defined","explanation":"Any aggregate with fields must define operator ? for EK9's tri-state semantics. Without it, guard expressions, coalescing, and safe access cannot inspect field state. Use 'default operator ?', 'default operator', or implement manually. See ek9 -h E07235 for details."},{"error":"E50060","correct":"stdout.println(`Sum: ${sum}`)","incorrect":"stdout.println(sum.toString())","explanation":"EK9 does not have a 'toString()' method. Use the $ operator for string conversion: '$sum' or string interpolation '`${sum}`'. See ek9 -h E50060 for details."},{"error":"E50001","correct":"precision <- 2","incorrect":"decimalPlaces <- 2","explanation":"Renaming the field to 'decimalPlaces' breaks all references to 'precision' in the constructor and operators. Field names must be consistent throughout the class. See ek9 -h E50001 for details."}],"companions":[]}
{"id":97,"category":"Classes and OOP","question":"What is the difference between classes and records in EK9?","url":"https://ek9.io/qa/QA0097.html","alternatePhrasings":["When should I use a record instead of a class in EK9?","How do records differ from classes in EK9?","What are EK9 records used for?"],"answer":"Classes and records serve different purposes in EK9. Classes encapsulate behaviour with private properties and methods. Records expose data with public properties and are ideal for eliminating data clumps.\n\nKEY DIFFERENCES\nClasses: properties always private, behaviour-focused, methods provide controlled access, can have methods.\nRecords: properties always public, data-focused, direct field access, constructors and operators only (no methods).\nBoth support constructors, operators, and inheritance.\n\nINHERITANCE — SAME OPT-IN RULE FOR BOTH\nRecords, like classes, are closed-by-default. Mark a base record (or class) 'as open' to allow extension. A child can override operators and call 'super.<op>(...)' to chain into the parent. See Q1297 for the full record-to-record inheritance pattern. See Q101 for closed-by-default rationale, Q102 for 'as open' details.\n\nRECORDS ELIMINATE DATA CLUMPS\nWhen multiple parameters appear together across several functions and methods, that is a data clump. Introducing a record groups them into a named type:\n  defines record\n    Coordinate\n      x <- 0.0\n      y <- 0.0\nInstead of passing x and y separately to every function, pass a single Coordinate. This reduces parameter counts, improves readability, and gives the group a meaningful name.\n\nMUTABILITY VIA PURE, NOT IMMUTABLE TYPES\nUnlike Java records (immutable) or Kotlin data classes (val vs var), EK9 records are mutable. Mutability is controlled at the function and method level via 'as pure', not at the data structure level. A pure function cannot modify any of its parameters, so the same record is safely mutable in normal code and protected in pure contexts. This preserves the Liskov Substitution Principle: a mutable subtype can always be used where the parent is expected. Languages that make records immutable break LSP because an immutable subtype cannot substitute for a mutable parent.\n\nWHEN TO USE WHICH\nUse a record when: you have a data clump, need a data transfer object, or want transparent data structures that functions operate on.\nUse a class when: you need to hide implementation details, enforce invariants through methods, or have complex behaviour tied to state.\n\nSee Q93 for class basics. See Q95 for visibility details. See Q96 for operators across constructs. See Q98 for record operators. See Q116 for default operator. See Q119 for constructs overview. See Q241 for mutation operators.","ek9Example":"defines module qa.oop.classvsrecord\n\n  defines record\n\n    <?-\n      Groups x and y into a named type,\n      eliminating the data clump of passing\n      two separate Float parameters everywhere.\n    -?>\n    Coordinate\n      x <- 0.0\n      y <- 0.0\n\n      Coordinate()\n        ->\n          x as Float\n          y as Float\n        this.x: x\n        this.y: y\n\n      default operator\n\n  defines class\n\n    Position\n      x <- 0.0\n      y <- 0.0\n\n      Position()\n        ->\n          x as Float\n          y as Float\n        this.x: x\n        this.y: y\n\n      x() as pure\n        <- rtn as Float: x\n\n      y() as pure\n        <- rtn as Float: y\n\n      default operator\n\n  defines function\n\n    <?-\n      Pure function: Coordinate is safely passed in\n      without risk of mutation. No immutable type needed.\n    -?>\n    distanceFromOrigin() as pure\n      -> point as Coordinate\n      <- rtn as Float: sqrt(point.x * point.x + point.y * point.y)\n\n    <?-\n      Normal function: can modify the record freely.\n      Same type works in both pure and non-pure contexts.\n    -?>\n    translate()\n      ->\n        point as Coordinate\n        dx as Float\n        dy as Float\n      point.x += dx\n      point.y += dy\n\n  defines program\n\n    ClassVsRecordDemo()\n      stdout <- Stdout()\n\n      // === RECORD: public fields, direct access ===\n\n      coord <- Coordinate(3.0, 4.0)\n      stdout.println(`Record x: ${coord.x}, y: ${coord.y}`)\n\n      // === RECORDS ELIMINATE DATA CLUMPS ===\n      // Instead of passing x and y separately, pass Coordinate\n\n      dist <- distanceFromOrigin(coord)\n      stdout.println(`Distance from origin: ${dist}`)\n\n      // === PURE CONTROLS MUTABILITY, NOT THE TYPE ===\n      // Same Coordinate is mutable here, protected in pure functions\n\n      translate(coord, 1.0, 2.0)\n      stdout.println(`After translate: ${coord.x}, ${coord.y}`)\n\n      // === CLASS: private fields, accessor methods ===\n\n      pos <- Position(3.0, 4.0)\n      stdout.println(`Class x: ${pos.x()}, y: ${pos.y()}`)\n\n      // === BOTH support operators ===\n\n      coord2 <- Coordinate(3.0, 4.0)\n      stdout.println(`Records equal: ${coord == coord2}`)\n\n      pos2 <- Position(3.0, 4.0)\n      stdout.println(`Classes equal: ${pos == pos2}`)\n\n      // === Records ideal for data transfer ===\n\n      stdout.println(`Coordinate: ${coord}`)\n      stdout.println(`Position: ${pos}`)","migrationContext":"Java: records (Java 16+) are immutable with auto-generated accessors, breaking LSP for mutable hierarchies. Python: dataclasses are mutable by default, frozen=True makes them immutable. Rust: structs are mutable by default, 'mut' controls mutability at the binding level. Go: only structs, all mutable, no immutability mechanism. Kotlin: data classes use val (immutable) or var (mutable) per field. Swift: structs are value types (copied on assignment), can have methods and computed properties unlike EK9 records, recommended over classes for most data types. EK9: records are mutable, mutability is controlled by 'as pure' on functions and methods rather than at the data structure level. This preserves LSP and separates the concern of data shape from data protection.","keywords":["Liskov","behaviour","class","data","data clump","difference","encapsulation","immutable","mutability","object-oriented","private","public","pure","record","struct","swift","transfer","visibility"],"primaryTopics":["class vs record","record","struct","data class"],"typicalErrors":[{"error":"E06180","correct":"pos.x()","incorrect":"pos.x","explanation":"Class properties are private by default. Accessing 'x' directly on a Position class instance fails; use the accessor method 'x()' instead. Records like Coordinate allow 'coord.x' directly. See ek9 -h E06180 for details."},{"error":"E50001","correct":"pos <- Position(3.0, 4.0)","incorrect":"pos <- Point(3.0, 4.0)","explanation":"There is no 'Point' type in this module. EK9 type names are case-sensitive and must match exactly. The class is called 'Position', not 'Point'. See ek9 -h E50001 for details."},{"error":"E07290","correct":"default operator","incorrect":"getName()\n        <- rtn as String: name","explanation":"Records can only have constructors and operators, not methods. Adding a method such as 'getName()' triggers E07290. Use a standalone function or switch to a class if you need methods. See ek9 -h E07290 for details."},{"error":"E50020","correct":"Position\n      x <- 0.0\n      y <- 0.0","incorrect":"Position extends Coordinate","explanation":"A class cannot extend a record because they are different construct types (genus). Classes extend classes and records extend records. Use composition instead of cross-genus inheritance. See ek9 -h E50020 for details."},{"error":"E50060","correct":"stdout.println(`Coordinate: ${coord}`)","incorrect":"stdout.println(coord.toString())","explanation":"EK9 does not have a 'toString()' method. Use the $ operator for string conversion: '$coord' or string interpolation '`${coord}`'. See ek9 -h E50060 for details."},{"error":"E50060","correct":"stdout.println(`Class x: ${pos.x()}, y: ${pos.y()}`)","incorrect":"stdout.println(`Class x: ${pos.getX()}, y: ${pos.y()}`)","explanation":"EK9 does not use Java-style 'getX()' accessors. Class accessor methods match the property name: 'pos.x()' not 'pos.getX()'. See ek9 -h E50060 for details."}],"companions":[]}
{"id":98,"category":"Classes and OOP","question":"How do record operators work in EK9?","url":"https://ek9.io/qa/QA0098.html","alternatePhrasings":["What operators can records have in EK9?","How do :=: and :~: and :^: operators work on records?","Why are record operators important for collections?","How do records support sorting, comparison, and serialisation?"],"answer":"EK9 records combine public fields with constructors AND operators, making them first-class in collections, serialisation, and comparison without external utility functions.\n\nCOMPARISON OPERATORS\n  <=> for ordering (sorting), == and <> for equality, <, >, <=, >= for relational.\nEnables stream sort, PriorityQueue, and generic ordered code.\n\nCONVERSION OPERATORS\n  $ to String (display), $$ to JSON (serialisation), #? hashcode (Dict keys).\n\nMUTATION OPERATORS\n  :=: deep copies all fields. :~: merges only SET fields (partial updates). :^: replaces entire content.\n\nDEFAULT OPERATOR\n  default operator\nAuto-generates all standard operators from fields. One line for full collection, serialisation, and comparison support.\n\nNO METHODS ON RECORDS\nRecords have only constructors and operators. Use functions or classes for methods.\n\nSee Q96 for operators across constructs. See Q97 for records vs classes. See Q116 for default operator. See Q120 for sorting. See Q238 for the fixed operator set. See Q241 for mutation rules.","ek9Example":"defines module qa.oop.recordops\n\n  defines record\n\n    <?-\n      A record with the full operator suite.\n      These operators make it work naturally\n      in collections, streams, and serialisation.\n    -?>\n    Config\n      host <- String()\n      port <- Integer()\n\n      Config()\n        ->\n          host as String\n          port as Integer\n        this.host: host\n        this.port: port\n\n      // === COMPARISON: enables sorting and deduplication ===\n\n      operator <=> as pure\n        -> other as Config\n        <- rtn as Integer: host <=> other.host\n\n      operator == as pure\n        -> other as Config\n        <- rtn as Boolean: host == other.host and port == other.port\n\n      operator <> as pure\n        -> other as Config\n        <- rtn as Boolean: not (this == other)\n\n      // === CONVERSION: enables display, logging, JSON, Dict keys ===\n\n      operator $ as pure\n        <- rtn as String: `${host}:${port}`\n\n      operator #? as pure\n        <- rtn as Integer: #?host + #?port\n\n      // === MUTATION: enables structured data transfer ===\n\n      operator :=:\n        -> from as Config\n        host :=: from.host\n        port :=: from.port\n\n      operator :~:\n        -> from as Config\n        if from.host?\n          host :=: from.host\n        if from.port?\n          port :=: from.port\n\n      operator :^:\n        -> from as Config\n        host :=: from.host\n        port :=: from.port\n\n      override operator ? as pure\n        <- rtn as Boolean: host? and port?\n\n  defines record\n\n    <?-\n      A record using 'default operator' to get\n      the full suite auto-generated from fields.\n    -?>\n    Endpoint\n      name <- String()\n      url <- String()\n      priority <- Integer()\n\n      Endpoint()\n        ->\n          name as String\n          url as String\n          priority as Integer\n        this.name: name\n        this.url: url\n        this.priority: priority\n\n      default operator\n\n  defines program\n\n    RecordOpsDemo()\n      stdout <- Stdout()\n\n      // === COMPARISON: records sort naturally in collections ===\n\n      configs <- [\n        Config(\"zeta-host\", 9090),\n        Config(\"alpha-host\", 8080),\n        Config(\"mu-host\", 3000)\n        ]\n\n      sorted <- cat configs | sort | collect as List of Config\n      stdout.println(\"Sorted configs:\")\n      cat sorted > stdout\n\n      // === EQUALITY: deduplication and lookup ===\n\n      c1 <- Config(\"localhost\", 8080)\n      c2 <- Config(\"localhost\", 8080)\n      c3 <- Config(\"production\", 443)\n      stdout.println(`Equal: ${c1 == c2}`)\n      stdout.println(`Different: ${c1 <> c3}`)\n\n      // === STRING AND HASHCODE: display, logging, Dict keys ===\n\n      stdout.println(`Config: ${c1}`)\n      stdout.println(`Hash: ${#?c1}`)\n\n      // === COPY: full deep copy ===\n\n      backup <- Config()\n      backup :=: c1\n      stdout.println(`Backup: ${backup}`)\n\n      // === MERGE: partial update (only set fields) ===\n\n      partial <- Config()\n      partial.host: \"override-host\"\n      target <- Config(\"original\", 3000)\n      target :~: partial\n      stdout.println(`After merge: ${target}`)\n\n      // === REPLACE: full replacement ===\n\n      replacement <- Config(\"new-host\", 9090)\n      target :^: replacement\n      stdout.println(`After replace: ${target}`)\n\n      // === DEFAULT OPERATOR: auto-generated full suite ===\n\n      endpoints <- [\n        Endpoint(\"api\", \"https://api.example.com\", 1),\n        Endpoint(\"web\", \"https://www.example.com\", 2),\n        Endpoint(\"api\", \"https://api.example.com\", 1)\n        ]\n\n      first <- endpoints.getOrDefault(0, Endpoint())\n      third <- endpoints.getOrDefault(2, Endpoint())\n      stdout.println(`First equals third: ${first == third}`)\n\n      sortedEndpoints <- cat endpoints | sort | collect as List of Endpoint\n      stdout.println(\"Sorted endpoints:\")\n      cat sortedEndpoints > stdout","migrationContext":"Java: records (16+) immutable, no copy/merge. Python: dataclasses, no merge. Rust: derive macros, no merge. Go: structs have no operators. EK9: records carry comparison, conversion, mutation operators. 'default operator' generates full suite from fields.","keywords":["JSON","collection","comparison","copy","data clump","default","field","hashcode","merge","mutation","operator","pipeline","record","replace","serialisation","sort","stream","string"],"primaryTopics":[],"typicalErrors":[{"error":"E07290","correct":"      // === COMPARISON: enables sorting and deduplication ===","incorrect":"      getDisplay() as pure\n        <- rtn as String: host\n\n      // === COMPARISON: enables sorting and deduplication ===","explanation":"Records can only have constructors and operators, not methods. Adding a method like getDisplay() triggers E07290. Use operators for conversion ($, $$) and access fields directly (config.host). If you need methods, use a class instead. See ek9 -h E07290 for details."},{"error":"E07235","correct":"      override operator ? as pure\n        <- rtn as Boolean: host? and port?\n\n  defines record","incorrect":"  defines record","explanation":"Records with fields must define operator ? for tri-state semantics. Without it, the record cannot participate in guard expressions or coalescing. Use 'default operator', 'default operator ?', or implement manually. See ek9 -h E07235 for details."},{"error":"E07500","correct":"operator <=> as pure","incorrect":"operator <=>","explanation":"Comparison operators must be marked 'as pure' because they should not have side effects. Omitting 'as pure' on ==, <>, <=>, <, >, <=, >= triggers E07500. See ek9 -h E07500 for details."}],"companions":[]}
{"id":99,"category":"Classes and OOP","question":"How do I create an enumeration in EK9?","url":"https://ek9.io/qa/QA0099.html","alternatePhrasings":["What is the syntax for enums in EK9?","How do EK9 enumerations work?","How do I define an enum type in EK9?","What operators do enumerations get automatically?"],"answer":"Enumerations are defined under 'defines type' with a value list. They automatically get 24 operators and 3 constructors with no boilerplate.\n\nBASIC ENUMERATION\n  defines type\n    Season\n      Spring, Summer, Autumn, Winter\nValues ordered by declaration.\n\nAUTOMATIC OPERATORS (24 + 3 constructors)\n  Comparison: ==, <>, <, >, <=, >=, <=> (same-type and String)\n  Conversion: $ (String), #^ (promote), $$ (JSON)\n  Hash/Set: #?, ?\n  First/Last: #<, #>\n  Constructors: Season() (unset), Season(Season) (copy), Season(String) (from string)\n\nTRI-STATE\n  Season() is unset. Season(\"Invalid\") returns unset. ? checks state. No exceptions on invalid input.\n\nITERATION\n  for season in Season\nOr: cat Season > stdout, cat Season | collect as List of Season.\n\nSWITCH\nCompiler requires all enum values listed in cases (E07310). Duplicates detected (E02060).\n\nNO METHODS\nEnums are pure value types. No methods, fields, or custom constructors. Use functions for behaviour, Dicts for associated data.\n\nSee Q73 for exhaustive switch. See Q96 for operators. See Q100 for Java comparison. See Q219 for free operators. See Q223 for constrained enums. See Q224 for enum streams. See Q238 for the fixed operator set.","ek9Example":"defines module qa.oop.enumeration\n\n  defines type\n\n    Season\n      Spring\n      Summer\n      Autumn\n      Winter\n\n  defines function\n\n    describeSeason() as pure\n      -> season as Season\n      <- description <- String()\n\n      switch season\n        case Season.Spring\n          description: \"flowers bloom\"\n        case Season.Summer\n          description: \"warm and sunny\"\n        case Season.Autumn\n          description: \"leaves falling\"\n        case Season.Winter\n          description: \"cold and snowy\"\n        default\n          description: \"no season set\"\n\n  defines program\n\n    EnumerationDemo()\n      stdout <- Stdout()\n\n      // === BASIC ENUMERATION: qualified access ===\n\n      favourite <- Season.Summer\n      stdout.println(`Favourite: ${favourite}`)\n\n      // === AUTOMATIC COMPARISON OPERATORS ===\n\n      stdout.println(`Spring < Winter: ${Season.Spring < Season.Winter}`)\n      stdout.println(`Summer == Summer: ${Season.Summer == Season.Summer}`)\n\n      // === STRING COMPARISON: enum vs String directly ===\n\n      stdout.println(`Direct string compare: ${favourite == \"Summer\"}`)\n\n      // === FIRST AND LAST ===\n\n      stdout.println(`First: ${#< favourite}`)\n      stdout.println(`Last: ${#> favourite}`)\n\n      // === CONVERSION: $, #? ===\n\n      asString <- $favourite\n      stdout.println(`As string: ${asString}`)\n      stdout.println(`Hash: ${#?favourite}`)\n\n      // === TRI-STATE: unset enumerations ===\n\n      unset <- Season()\n      stdout.println(`Unset isSet: ${unset?}`)\n\n      // === STRING CONSTRUCTION: safe, no exceptions ===\n\n      fromString <- Season(\"Autumn\")\n      stdout.println(`From valid string: ${fromString}`)\n\n      invalid <- Season(\"Monsoon\")\n      stdout.println(`From invalid string isSet: ${invalid?}`)\n\n      // === GUARD EXPRESSION: safe string parsing ===\n\n      if parsed <- Season(\"Winter\")\n        stdout.println(`Parsed: ${parsed}`)\n\n      // === ITERATION: for...in ===\n\n      stdout.println(\"All seasons:\")\n      for season in Season\n        stdout.println(`  ${season}: ${describeSeason(season)}`)\n\n      // === STREAM PIPELINE ===\n\n      seasons <- cat Season | collect as List of Season\n      stdout.println(`Count: ${length seasons}`)\n\n      // === STREAM TO OUTPUT ===\n\n      stdout.println(\"Streamed:\")\n      cat Season > stdout\n\n      // === SWITCH WITH MULTIPLE CASE VALUES ===\n\n      for season in Season\n        category <- switch season\n          <- rtn as String: String()\n          case Season.Spring, Season.Summer\n            rtn: \"warm half\"\n          case Season.Autumn, Season.Winter\n            rtn: \"cold half\"\n          default\n            rtn: \"unknown\"\n        stdout.println(`${season} is ${category}`)","migrationContext":"Java: enum valueOf() throws on invalid. Python: Enum['NAME'] raises KeyError. Go: iota constants, no type safety. EK9: value list, 24 auto operators, invalid string returns unset, for...in iteration, no methods.","keywords":["JSON","automatic","comparison","composition","constructor","enum","enumeration","first","hashcode","iteration","last","operator","pipeline","stream","string","switch","tri-state","type","unset","value"],"primaryTopics":["enum","enumeration","define enum"],"typicalErrors":[{"error":"E07630","correct":"$favourite","incorrect":"$Season","explanation":"The $ operator works on enumeration INSTANCES, not the enumeration TYPE. Use $ on variables like $favourite, not on the type name Season. See ek9 -h E07630 for details."},{"error":"E01050","correct":"Spring\n      Summer\n      Autumn\n      Winter","incorrect":"Spring\n      Summer\n      SPRING\n      Winter","explanation":"EK9 normalizes enumeration names (uppercase + remove underscores) to detect confusing duplicates. Spring and SPRING both normalize to SPRING, triggering E01050. Use clearly distinct names. See ek9 -h E01050 for details."},{"error":"E07310","correct":"case Season.Autumn\n          description: \"leaves falling\"\n        case Season.Winter\n          description: \"cold and snowy\"\n        default","incorrect":"default","explanation":"When switching on an enumeration, the compiler requires all values to be explicitly listed in case clauses. Removing Autumn and Winter cases leaves the switch non-exhaustive, triggering E07310. See ek9 -h E07310 for details."},{"error":"E02060","correct":"case Season.Autumn\n          description: \"leaves falling\"","incorrect":"case Season.Spring\n          description: \"flowers bloom\"","explanation":"Duplicate enumeration values in switch cases are detected at compile time. Replacing Autumn with a second Spring creates a duplicate, triggering E02060. See ek9 -h E02060 for details."}],"companions":[],"oracleToolHint":{"tool":"ek9_scaffold","intent":"type","description":"Oracle can generate an enumeration type with named values."}}
{"id":100,"category":"Classes and OOP","question":"How do EK9 enumerations differ from Java enums?","url":"https://ek9.io/qa/QA0100.html","alternatePhrasings":["Why are EK9 enums simpler than Java enums?","Can EK9 enums have methods and fields like Java?","What can Java enums do that EK9 enumerations cannot?","How do I migrate Java enum patterns to EK9?"],"answer":"EK9 enumerations are deliberately simpler than Java enums. They are pure value types with no methods, no fields, and no associated data. Every enum automatically gets 24 capabilities: 14 comparison operators (same-type and string), $, $$, #?, ?, #<, #>, #^, three constructors, and built-in iteration.\n\nKEY DIFFERENCES FROM JAVA\n1. No methods or fields — behaviour goes in standalone functions, data in Dicts (composition over enum methods).\n2. Safe string construction — Priority(\"Invalid\") returns unset, not IllegalArgumentException.\n3. Direct string comparison — 'priority == \"High\"' works without conversion.\n4. Exhaustive switch — adding a value forces updating ALL switches (E07310). Java's default silently swallows new values.\n\nEK9 COMPOSITION PATTERN\n  Java: Priority.HIGH.getLabel() with field on enum\n  EK9: priorityLabel(Priority.High) with standalone function\n\nSee Q99 for creating enumerations. See Q73 for exhaustive enum switch. See Q219 for auto-generated operators. See Q225 for the composition pattern in detail.","ek9Example":"defines module qa.oop.enumvsjava\n\n  defines type\n\n    Priority\n      Low\n      Medium\n      High\n      Critical\n\n  defines function\n\n    <?-\n      In Java this would be a method ON the enum.\n      In EK9 behaviour lives in standalone functions.\n    -?>\n    priorityLabel() as pure\n      -> priority as Priority\n      <- label as String: switch priority\n        <- rtn as String: String()\n        case Priority.Low\n          rtn: \"Low - handle when convenient\"\n        case Priority.Medium\n          rtn: \"Medium - handle soon\"\n        case Priority.High\n          rtn: \"High - handle today\"\n        case Priority.Critical\n          rtn: \"Critical - handle immediately\"\n        default\n          rtn: \"No priority set\"\n\n    isUrgent() as pure\n      -> priority as Priority\n      <- rtn as Boolean: priority >= Priority.High\n\n  defines program\n\n    EnumVsJavaDemo()\n      stdout <- Stdout()\n\n      // === ALL 24 AUTO-GENERATED CAPABILITIES ===\n\n      high <- Priority.High\n\n      //Same-type comparison (== <> < > <= >= <=>)\n      stdout.println(`Ordering: High > Low = ${high > Priority.Low}`)\n      stdout.println(`Compare: High <=> Medium = ${high <=> Priority.Medium}`)\n\n      //String comparison (== <> < > <= >= <=>)\n      stdout.println(`Equals string: ${high == \"High\"}`)\n      stdout.println(`Not equal string: ${high <> \"Low\"}`)\n\n      //String conversion ($ #^)\n      stdout.println(`Dollar: ${high}`)\n\n      //JSON serialization ($$)\n      stdout.println(`JSON: ${$high}`)\n\n      //Hash code (#?)\n      stdout.println(`Hash: ${#? high}`)\n\n      //Is set (?)\n      stdout.println(`Is set: ${high?}`)\n\n      //First / Last (#< #>)\n      stdout.println(`First: ${#< high}`)\n      stdout.println(`Last: ${#> high}`)\n\n      // === SAFE STRING CONSTRUCTION: no exception ===\n      // Java: Priority.valueOf(\"Urgent\") throws IllegalArgumentException\n      // EK9: Priority(\"Urgent\") returns unset\n\n      //Constructor from String (unset if no match)\n      fromString <- Priority(\"High\")\n      stdout.println(`Valid: ${fromString}, isSet: ${fromString?}`)\n\n      invalid <- Priority(\"Urgent\")\n      stdout.println(`Invalid isSet: ${invalid?}`)\n\n      //Copy constructor\n      copy <- Priority(high)\n      stdout.println(`Copy: ${copy}`)\n\n      //Default constructor (unset)\n      unsetPriority <- Priority()\n      stdout.println(`Unset isSet: ${unsetPriority?}`)\n\n      // === GUARD: safe string-to-enum parsing ===\n\n      if parsed <- Priority(\"Critical\")\n        stdout.println(`Parsed: ${parsed}`)\n\n      // === TRI-STATE: unset enum (no null in EK9) ===\n\n      stdout.println(priorityLabel(unsetPriority))\n\n      // === BEHAVIOUR VIA FUNCTIONS (replaces Java enum methods) ===\n\n      for priority in Priority\n        stdout.println(priorityLabel(priority))\n\n      // === DICT FOR ASSOCIATED DATA (replaces Java enum fields) ===\n\n      icons <- {\n        Priority.Low: \"...\",\n        Priority.Medium: \"(!)\",\n        Priority.High: \"(!!)\",\n        Priority.Critical: \"!!!\"\n        }\n\n      for priority in Priority\n        icon <- icons.getOrDefault(priority, \"?\")\n        stdout.println(`${priority}: ${icon}`)\n\n      // === STREAM PIPELINE: built-in iteration ===\n      // Java: Arrays.stream(Priority.values())\n      // EK9: cat Priority | ...\n\n      urgent <- cat Priority\n        | filter by isUrgent\n        | collect as List of Priority\n      stdout.println(`Urgent count: ${length urgent}`)","migrationContext":"Java: enums are full classes with fields/methods, valueOf() throws. Python: Enum with methods, KeyError on invalid. Kotlin: enum class with properties. EK9: pure value types, 24 auto-generated capabilities, no methods/fields, safe string construction returns unset.","keywords":["Dict","comparison","composition","construction","enumeration","exhaustive","field","function","java","method","migrate","operator","simple","string","switch","tri-state","type","unset","value","valueOf"],"primaryTopics":[],"typicalErrors":[{"error":"E50060","correct":"priorityLabel(priority)","incorrect":"priority.label()","explanation":"EK9 enumerations cannot have methods. Java developers often expect to call methods like label() on enum values. The compiler reports the method as not resolved. In EK9, behaviour belongs in standalone functions that take the enum as a parameter. See ek9 -h E50060 for details."},{"error":"E07620","correct":"stdout.println(`First: ${#< high}`)","incorrect":"stdout.println(`Negate: ${- high}`)","explanation":"Enumerations only support specific operators. Using negate (-) on an enum instance triggers E07620 — operator not defined. Enumerations support comparison, string conversion, isSet, first/last, and hash operators only. See ek9 -h E07620 for details."},{"error":"E07310","correct":"        case Priority.Critical\n          rtn: \"Critical - handle immediately\"","incorrect":"        //case Priority.Critical removed","explanation":"Every switch over an enumeration must cover ALL values. Removing a case triggers E07310 — cases should cover all enumerated values. EK9 does not allow silent omission even when a default is present. See ek9 -h E07310 for details."},{"error":"E07620","correct":"stdout.println(`Is set: ${high?}`)","incorrect":"stdout.println(`Negate: ${~high}`)","explanation":"Enumerations only support a specific set of operators: comparison (== <> < > <= >= <=>), string conversion ($ #^), JSON ($$), hash (#?), isSet (?), and first/last (#< #>). Using any other operator like bitwise negate (~) on an enum instance produces this error. See ek9 -h E07620 for details."},{"error":"E01050","correct":"Low\n      Medium\n      High\n      Critical","incorrect":"LOW\n      low\n      High\n      Critical","explanation":"EK9 normalizes enumeration names (uppercase + remove underscores) to detect confusing duplicates. LOW and low both normalize to LOW. Java allows case-different enum constants but EK9 prevents this source of confusion. See ek9 -h E01050 for details."}],"companions":[]}
{"id":101,"category":"Classes and OOP","question":"Why are types closed by default in EK9?","url":"https://ek9.io/qa/QA0101.html","alternatePhrasings":["Why can I not extend a class in EK9?","What does closed by default mean in EK9?","How does EK9 prevent fragile base class problems?","What are the three levels of type openness in EK9?"],"answer":"EK9 types are closed by default, meaning they cannot be extended unless explicitly marked with 'as open' or 'as abstract'. This prevents the fragile base class problem and enforces composition over inheritance.\n\nTHREE LEVELS OF TYPE OPENNESS\nEK9 provides three levels of control over type extension:\n  1. CLOSED (default) — cannot be extended at all\n  2. OPEN ('as open') — can be extended by any class\n  3. SEALED ('allow only') — can only be extended by explicitly named classes\nThis gives precise control: most types stay closed, some are selectively opened, and sealed types restrict extension to a known set.\n\nWHICH CONSTRUCTS SUPPORT OPEN AND ABSTRACT?\nFive constructs support 'as open' and 'as abstract': functions, records, traits, classes, and components. Traits are inherently open (designed to be implemented) so 'as open' on a trait is accepted for syntax consistency but has no additional effect. Services and text blocks are always closed — they cannot be extended.\n\nCLOSED BY DEFAULT PRINCIPLE\nWhen you define a class without modifiers, it is final:\n  defines class\n    Config\n      host <- String()\nAttempting to extend Config produces compile error E05030.\n\nSEALED TYPES WITH 'ALLOW ONLY'\nFor types that need limited extensibility, EK9 provides 'allow only' — the equivalent of Java 17's sealed classes and Kotlin's sealed classes. Both traits and classes support this:\n  Shape allow only Circle, Square, Triangle\n    area() as abstract\n      <- rtn as Float?\nOnly the named classes can implement Shape. Any other class attempting 'with trait of Shape' triggers E05240. Classes with 'allow only' must also be declared 'as open' since classes are closed by default.\n\nTHE PROBLEM WITH OPEN TYPES\nOpen types create maintenance risks: subclasses depend on implementation details, overriding methods can break invariants, and changes to the base class can silently break subclasses (fragile base class problem).\n\nHISTORICAL EVIDENCE\nJava's open-by-default ArrayList, HashMap, and other collection classes have been a source of bugs for decades. Subclassing collections mixes collection behaviour with application logic.\n\nMODERN TREND\nSwift structs are closed. Rust has no inheritance. Kotlin classes are final by default. Java added sealed classes in Java 17. EK9 follows this modern consensus and goes further by making closed the default.\n\nWHAT THIS MEANS IN PRACTICE\nUse composition and delegation instead of inheritance:\n  Employee\n    role as Role?\nThis approach is more flexible and avoids tight coupling.\n\nSee Q102 for making types extensible with 'as open'. See Q109 for composition patterns. See Q93 for class basics. See Q106 for traits as an alternative. See Q128 for why built-in collection types (List, Dict) are closed and the delegation pattern. See Q212 for composition over inheritance pattern. See Q268 for how closed types prevent OWASP access control vulnerabilities. See Q298 for sealed traits with 'allow only'. See Q301 for sealed classes with 'allow only'. See Q299 for sealed types with dispatchers.","ek9Example":"defines module qa.oop.closedbydefault\n\n  defines trait\n\n    SealedTrait allow only AllowedPrinter\n\n      describe() as abstract\n        <- rtn as String?\n\n  defines record\n\n    //Closed record - cannot be extended (no 'as open')\n    ClosedRecord\n      value <- 0\n      default operator\n\n    //Open record - can be extended\n    OpenRecord as open\n      name <- String()\n      default operator\n\n    ExtendedRecord extends OpenRecord\n      extra <- String()\n      default operator\n\n  defines class\n\n    Config\n      host <- \"localhost\"\n      port <- 8080\n\n      host() as pure\n        <- rtn as String: host\n\n      port() as pure\n        <- rtn as Integer: port\n\n      default operator\n\n    //Open class - can be extended\n    OpenClass as open\n      label <- String()\n\n      label() as pure\n        <- rtn as String: label\n\n      default operator\n\n    ExtendedClass extends OpenClass\n      detail <- String()\n      default operator\n\n    //Sealed trait implementation - allowed\n    AllowedPrinter with trait of SealedTrait\n      override describe()\n        <- rtn as String: \"allowed\"\n\n      default operator\n\n    //Not in the allow only list\n    UnlistedPrinter\n\n      unlisted()\n        <- rtn as String: \"not allowed\"\n\n      default operator\n\n    ServerRunner\n      config <- Config()\n\n      ServerRunner()\n        -> config as Config\n        this.config: config\n\n      describe()\n        <- rtn as String: `Server at ${config.host()}:${config.port()}`\n\n      default operator ?\n\n  defines program\n\n    ClosedByDefaultDemo()\n      stdout <- Stdout()\n\n      // === CLOSED BY DEFAULT ===\n\n      config <- Config()\n      stdout.println(`Config: ${config}`)\n\n      // === OPEN CLASS CAN BE EXTENDED ===\n\n      ext <- ExtendedClass()\n      stdout.println(`Extended: ${ext}`)\n\n      // === OPEN RECORD CAN BE EXTENDED ===\n\n      rec <- ExtendedRecord()\n      stdout.println(`Extended record: ${rec}`)\n\n      // === SEALED TRAIT — ONLY ALLOWED TYPES ===\n\n      allowed <- AllowedPrinter()\n      stdout.println(`Sealed: ${allowed.describe()}`)\n\n      // === COMPOSITION INSTEAD OF INHERITANCE ===\n\n      server <- ServerRunner(config)\n      stdout.println(server.describe())","migrationContext":"Java: classes are open by default, must use 'final' to prevent extension (most developers forget); sealed classes added in Java 17 with 'permits' clause. Python: all classes are open, no way to prevent extension. Rust: no inheritance, uses traits and composition exclusively. Go: no inheritance, only interface embedding and struct composition. Kotlin: classes are final by default, must use 'open' keyword; sealed classes restrict subclasses to same file. Swift: classes are final by default with 'final' keyword, structs cannot be inherited. EK9: three levels — closed (default), open ('as open'), sealed ('allow only' with named permitted types). Closed by default like Kotlin, with sealed types equivalent to Java 17 sealed classes.","keywords":["abstract","allow","base","class","closed","component","composition","default","exhaustive","final","fragile","function","inheritance","migrate","object-oriented","only","open","permit","record","restrict","sealed","swift","trait"],"primaryTopics":["closed type","sealed class","final class","open closed"],"typicalErrors":[{"error":"E05030","correct":"ExtendedClass extends OpenClass","incorrect":"ExtendedClass extends Config","explanation":"Config is closed by default (no 'as open' modifier). Attempting to extend it triggers E05030 — not open to be extended. EK9 types are closed by default to prevent the fragile base class problem. Use composition instead. See ek9 -h E05030 for details."},{"error":"E05030","correct":"ExtendedRecord extends OpenRecord","incorrect":"ExtendedRecord extends ClosedRecord","explanation":"ClosedRecord has no 'as open' modifier, so it cannot be extended. Records are also closed by default, just like classes. See ek9 -h E05030 for details."},{"error":"E06180","correct":"config.host()","incorrect":"config.host","explanation":"Class properties are private by default in EK9. Accessing 'host' directly from outside the class fails; use the accessor method 'host()' instead. See ek9 -h E06180 for details."},{"error":"E05240","correct":"UnlistedPrinter\n\n      unlisted()","incorrect":"UnlistedPrinter with trait of SealedTrait\n\n      override describe()","explanation":"When a trait uses 'allow only' to restrict implementations, only the named classes can implement it. A class not in the list triggers E05240 — type is not permitted to extend/implement this sealed type. This is EK9's equivalent of Java's sealed interfaces. See ek9 -h E05240 for details."}],"companions":[]}
{"id":102,"category":"Classes and OOP","question":"How do I make a class extensible with 'as open'?","url":"https://ek9.io/qa/QA0102.html","alternatePhrasings":["How do I allow a class to be extended in EK9?","What does 'as open' mean on a class?","How does inheritance work in EK9?"],"answer":"In EK9, use 'as open' to allow a class to be extended. Without it, classes are closed by default (E05030). Abstract classes are automatically open since they must be extended to be useful.\n\nTHE AS OPEN MODIFIER\nMark a class as open to allow subclasses:\n  Shape as open\n    name <- String()\nNow other classes can extend Shape. Without 'as open', attempting to extend Shape triggers E05030.\n\nWHICH CONSTRUCTS SUPPORT AS OPEN?\nFive constructs support 'as open': functions, records, classes, components, and traits. Traits are inherently open (designed to be implemented) so 'as open' on a trait is accepted for syntax consistency but has no additional effect. Services and text blocks cannot be extended.\n\nABSTRACT CLASSES ARE AUTOMATICALLY OPEN\nAbstract classes are inherently open because they require concrete subclasses:\n  Vehicle as abstract\n    speed() as abstract\n      <- rtn as Float?\n'as abstract' implies 'as open' — you never need both.\n\nINHERITANCE SYNTAX\nUse 'extends' or 'is' to inherit:\n  Circle extends Shape\n  Circle is Shape\nBoth are equivalent. Use 'override' for overridden methods — it is mandatory in EK9, not optional like in Java.\n\nOVERRIDE IS MANDATORY\nUnlike Java where @Override is an annotation you can forget, EK9 requires the 'override' keyword on any method that overrides a parent method. Omitting it triggers E05120. This prevents accidental shadowing.\n\nSEALED ALTERNATIVE: ALLOW ONLY\nIf you want limited extensibility rather than fully open, use 'allow only' to restrict which classes can extend:\n  Shape allow only Circle, Square, Triangle as open\nThis is EK9's equivalent of Java 17's sealed classes. See Q298-Q301 for details.\n\nWHEN TO USE AS OPEN\nUse 'as open' when you deliberately design a type for extension. Keep open types small in number — most types should remain closed. Prefer composition (Q109) and traits (Q106) over inheritance where possible.\n\nSee Q101 for why types are closed by default. See Q103 for abstract classes. See Q93 for class basics. See Q106 for traits. See Q570 for override mechanics in open class hierarchies. See Q298 for sealed traits with 'allow only'. See Q301 for sealed classes.","ek9Example":"defines module qa.oop.asopen\n\n  defines class\n\n    Shape as open\n      name <- \"shape\"\n\n      Shape()\n        -> name as String\n        this.name: name\n\n      name() as pure\n        <- rtn as String: name\n\n      describe()\n        <- rtn as String: \"I am a \" + name\n\n      default operator\n\n    Circle extends Shape\n      radius <- 0.0\n\n      Circle()\n        -> radius as Float\n        super(\"circle\")\n        this.radius: radius\n\n      radius() as pure\n        <- rtn as Float: radius\n\n      override describe()\n        <- rtn as String: `I am a circle with radius ${radius}`\n\n      default operator ?\n\n    Vehicle as abstract\n      speed() as abstract\n        <- rtn as Float?\n\n      describe()\n        <- rtn as String: `Vehicle at ${speed()} mph`\n\n      default operator ?\n\n    Car is Vehicle\n      topSpeed <- 120.0\n\n      Car()\n        -> topSpeed as Float\n        this.topSpeed: topSpeed\n\n      override speed()\n        <- rtn as Float: topSpeed\n\n      default operator ?\n\n  defines record\n\n    Coordinate\n      xPos as Float: 0.0\n      yPos as Float: 0.0\n\n      default operator ?\n\n  defines program\n\n    AsOpenDemo()\n      stdout <- Stdout()\n\n      // === AS OPEN: explicit extension ===\n\n      shape <- Shape(\"triangle\")\n      stdout.println(shape.describe())\n\n      circle <- Circle(5.0)\n      stdout.println(circle.describe())\n\n      // === ABSTRACT: automatically open ===\n\n      car <- Car(150.0)\n      stdout.println(car.describe())\n\n      // === OVERRIDE ===\n\n      stdout.println(`Circle name: ${circle.name()}`)\n      stdout.println(`Car speed: ${car.speed()}`)","migrationContext":"Java: all classes are open by default, use 'final' to close; @Override annotation is optional (easily forgotten); sealed classes added in Java 17 with 'permits'. Python: all classes open, no way to seal. Rust: no inheritance at all, traits only. Go: no inheritance, embedding only. Kotlin: classes final by default, 'open' keyword to allow extension (same approach as EK9), 'override' keyword mandatory (same as EK9). Swift: classes not final by default but 'final' keyword available. EK9: closed by default, 'as open' to allow extension, 'as abstract' is automatically open, 'extends' or 'is' for inheritance, 'override' mandatory not optional, 'allow only' for sealed types.","keywords":["abstract","allow","base","child","class","component","define","extends","extensible","function","inherit","inheritance","is","modifier","object-oriented","only","open","override","parent","record","sealed","subclass","super","virtual"],"primaryTopics":["as open","open class","extensible","inheritance"],"typicalErrors":[{"error":"E05120","correct":"override describe()","incorrect":"describe()","explanation":"When overriding a method from a parent class, the 'override' keyword is mandatory. Omitting it on Circle's describe() triggers E05120 because the method exists in Shape. See ek9 -h E05120 for details."},{"error":"E05030","correct":"Shape as open","incorrect":"Shape","explanation":"Without 'as open', Shape would be closed by default and Circle could not extend it. Attempting to extend a closed class triggers E05030 — not open to be extended. EK9 types are closed by default to prevent the fragile base class problem. See ek9 -h E05030 for details."},{"error":"E05120","correct":"override speed()","incorrect":"speed()","explanation":"Car must use 'override' when implementing the abstract method speed() from Vehicle. Omitting 'override' triggers E05120. See ek9 -h E05120 for details."},{"error":"E50020","correct":"Circle extends Shape","incorrect":"Circle extends Coordinate","explanation":"A class defined in 'defines class' cannot extend a record from 'defines record' because they are different construct types (genus). Classes extend classes, records extend records. See ek9 -h E50020 for details."},{"error":"E50060","correct":"Circle extends Shape","incorrect":"Circle extends Vehicle","explanation":"Circle's constructor calls super(\"circle\") which expects Shape's single-String constructor. Vehicle has a different constructor signature (make, year). Extending Vehicle with Shape's super call triggers E50060 — constructor not resolved. See ek9 -h E50060 for details."}],"companions":[],"oracleToolHint":{"tool":"ek9_scaffold","intent":"class","description":"Oracle can generate an extensible class with 'as open' modifier and correct inheritance structure."}}
{"id":103,"category":"Classes and OOP","question":"What are abstract classes and methods in EK9?","url":"https://ek9.io/qa/QA0103.html","alternatePhrasings":["How do I create an abstract class in EK9?","How do abstract methods work in EK9?","When should I use abstract vs trait in EK9?"],"answer":"Abstract classes define incomplete types that must be extended with concrete implementations. They can have both implemented and abstract methods, and they can hold state (properties).\n\nABSTRACT CLASSES\nMark a class as abstract with 'as abstract':\n  Formatter as abstract\n    prefix <- String()\n    format() as abstract\n      -> text as String\n      <- rtn as String?\nAbstract classes cannot be instantiated directly (E50080) — they are incomplete types. 'as abstract' implies 'as open' — you never need both.\n\nWHICH CONSTRUCTS SUPPORT AS ABSTRACT?\nFive constructs support 'as abstract': functions, records, classes, components, and traits. Abstract functions are unique to EK9 among mainstream languages. A method without a body must be declared 'as abstract' (E07110).\n\nABSTRACT METHODS\nAbstract methods have no body. Subclasses must provide implementations using the mandatory 'override' keyword:\n  override format()\n    -> text as String\n    <- rtn as String: prefix + data\nOmitting 'override' triggers E05120. The compiler ensures all abstract methods are implemented — omitting any triggers E07140.\n\nABSTRACT FUNCTIONS\nEK9 also supports abstract functions:\n  defines function\n    Parser as abstract\n      -> input as String\n      <- result as String?\nConcrete functions extend abstract ones with 'is' or 'extends'.\n\nWHEN TO USE ABSTRACT VS TRAIT\nUse abstract class when: you need shared state (properties), constructors, and partial implementation with a single inheritance chain.\nUse trait when: you need pure behaviour contracts that multiple unrelated classes can implement. EK9 supports multiple traits but single class inheritance.\n\nSee Q102 for 'as open'. See Q101 for closed by default. See Q51 for abstract functions. See Q106 for traits. See Q110 for trait vs abstract comparison. See Q111 for components. See Q263 for factory pattern returning abstract class implementations. See Q560 for purity contracts on abstract methods. See Q570 for override mechanics. See Q580 for constructor delegation in class hierarchies.","ek9Example":"defines module qa.oop.abstractclasses\n\n  defines function\n\n    Parser as pure abstract\n      -> input as String\n      <- result as String?\n\n    JsonParser is Parser as pure\n      -> input as String\n      <- result as String: \"parsed:\" + input\n\n  defines class\n\n    Formatter as abstract\n      prefix <- String()\n\n      Formatter()\n        -> prefix as String\n        this.prefix: prefix\n\n      format() as abstract\n        -> text as String\n        <- rtn as String?\n\n      prefix() as pure\n        <- rtn as String: prefix\n\n      default operator ?\n\n    BracketFormatter extends Formatter\n      BracketFormatter()\n        super(\"[\")\n\n      override format()\n        -> text as String\n        <- rtn as String: `${prefix()}${text}]`\n\n      default operator ?\n\n    HtmlFormatter extends Formatter\n      tag <- \"p\"\n\n      HtmlFormatter()\n        -> tag as String\n        super(`<${tag}>`)\n        this.tag: tag\n\n      override format()\n        -> text as String\n        <- rtn as String: `${prefix()}${text}</${tag}>`\n\n      default operator ?\n\n  defines program\n\n    AbstractDemo()\n      stdout <- Stdout()\n\n      // === ABSTRACT CLASS with concrete subclass ===\n\n      bracket <- BracketFormatter()\n      stdout.println(bracket.format(\"hello\"))\n\n      html <- HtmlFormatter(\"div\")\n      stdout.println(html.format(\"content\"))\n\n      // === ABSTRACT FUNCTION ===\n\n      result <- JsonParser(\"data\")\n      stdout.println(result)\n\n      // === Polymorphism ===\n\n      formatters <- List() of Formatter\n      formatters += BracketFormatter()\n      formatters += HtmlFormatter(\"span\")\n\n      for formatter in formatters\n        stdout.println(formatter.format(\"test\"))","migrationContext":"Java: abstract class with abstract methods, can have state and constructors, single inheritance. Python: ABC with @abstractmethod decorator, no enforcement at parse time. Rust: no abstract classes, use traits with default implementations. Go: no abstract types, interfaces are implicitly implemented. Kotlin: abstract class with abstract members, can have state, single inheritance. EK9: 'as abstract' modifier, abstract methods with no body, abstract functions (unique), automatically open for extension, subclasses use 'override'.","keywords":["abstract","class","component","concrete","extends","function","hierarchy","implement","incomplete","is","method","object-oriented","open","override","record","state","trait","virtual"],"primaryTopics":["abstract class","abstract method"],"typicalErrors":[{"error":"E05120","correct":"override format()","incorrect":"format()","explanation":"When implementing an abstract method from a parent class, the 'override' keyword is mandatory. Omitting it on BracketFormatter's format() triggers E05120. See ek9 -h E05120 for details."},{"error":"E50010","correct":"BracketFormatter extends Formatter","incorrect":"BracketFormatter extends SomeClosedClass","explanation":"Only abstract or open classes can be extended. Abstract classes like Formatter are automatically open. Extending a closed class triggers E50010 — not open to be extended. See ek9 -h E50010 for details."},{"error":"E07110","correct":"format() as abstract\n        -> text as String\n        <- rtn as String?","incorrect":"format()\n        -> text as String\n        <- rtn as String?","explanation":"A method without a body must be declared 'as abstract'. Omitting the modifier triggers E07110 — implementation not provided so must be declared as abstract. See ek9 -h E07110 for details."},{"error":"E50020","correct":"BracketFormatter extends Formatter","incorrect":"BracketFormatter extends JsonParser","explanation":"A class cannot extend a function because they are different construct types (genus). Classes extend classes and functions extend functions. See ek9 -h E50020 for details."},{"error":"E07130","correct":"      override format()\n        -> text as String\n        <- rtn as String: `${prefix()}${text}]`\n\n      default operator ?","incorrect":"      default operator ?","explanation":"BracketFormatter extends abstract Formatter but must implement all abstract methods. Removing the override of format() leaves the abstract method unimplemented, triggering E07130 — all abstract methods must be implemented in concrete subclasses. See ek9 -h E07130 for details."},{"error":"E50080","correct":"bracket <- BracketFormatter()","incorrect":"fmt <- Formatter()","explanation":"Abstract classes cannot be instantiated directly — they are incomplete types with unimplemented methods. Attempting to call Formatter() triggers E50080 — cannot make a call on an abstract function/type directly. Create a concrete subclass instead. See ek9 -h E50080 for details."},{"error":"E50040","correct":"Formatter as abstract\n      prefix <- String()","incorrect":"Formatter as open\n      prefix <- String()","explanation":"An abstract method can only exist inside an abstract class. If the class is not marked 'as abstract' but contains an abstract method, E50040 is triggered — cannot be abstract in a non-abstract construct. Either mark the class 'as abstract' or provide a method body. See ek9 -h E50040 for details."},{"error":"E50080","correct":"html <- HtmlFormatter(\"div\")","incorrect":"html <- Formatter(\"div\")","explanation":"Abstract classes cannot be instantiated directly — they are incomplete types with unimplemented methods. Attempting to call Formatter() triggers E50080 — cannot make a call on an abstract function/type directly. Create a concrete subclass instead. See ek9 -h E50080 for details."}],"companions":[]}
{"id":104,"category":"Classes and OOP","question":"Why does EK9 require explicit constructors for uninitialised properties?","url":"https://ek9.io/qa/QA0104.html","alternatePhrasings":["What happens if a class property has no default value in EK9?","How do I handle uninitialised fields in an EK9 class?","Why must I write a constructor when a property is not initialised?"],"answer":"EK9 requires that every property is either given a default value at declaration or initialised in a constructor. If a property is declared without a value, the compiler requires a constructor that sets it. This prevents null surprises.\n\nTHE RULE\nProperties declared without values must be initialised in a constructor:\n  Connection\n    host as String?\n    port as Integer?\n    Connection()\n      -> host as String, port as Integer\n      this.host: host\n      this.port: port\nThe '?' suffix means 'declared but not yet initialised'. Without '?' the compiler rejects the declaration immediately with E08180.\n\nFLOW ANALYSIS ENFORCEMENT\nEK9 uses flow analysis to verify that every property is initialised before use:\n  E08180 — property not marked '?' and not initialised (must use '?' or provide a value)\n  E08070 — property declared with '?' but never assigned in any constructor\n  E08060 — property used before it has been initialised (would be NullPointerException in Java)\nThis eliminates the entire category of 'field not initialised' bugs at compile time.\n\nWHY THIS MATTERS\nIn Java, fields default to null. You discover the problem at runtime with NullPointerException. In Python, missing attributes cause AttributeError at runtime. In Kotlin, lateinit crashes at runtime if accessed before init. EK9 catches ALL of these at compile time.\n\nHOW TO SATISFY THE RULE\nOption 1: Provide a default value at declaration:\n  host <- \"localhost\"\nOption 2: Use unset default (type constructor):\n  host <- String()\nOption 3: Declare without value and initialise in constructor:\n  host as String?\n  MyClass() -> host as String; this.host: host\nNote: property initialisers must be simple — literals, type constructors, or literal collections only. Function calls and expressions are not allowed (E04050). Use a constructor for computed values.\n\nSee Q29 for unset variables. See Q94 for constructors. See Q93 for class basics. See Q632 for define-before-use patterns. See Q633 for initialisation across branches. See Q692 for field initialisation patterns.","ek9Example":"defines module qa.oop.uninitialised\n\n  defines class\n\n    Connection\n      host as String?\n      port as Integer?\n\n      default private Connection()\n\n      Connection()\n        ->\n          host as String\n          port as Integer\n        this.host: host\n        this.port: port\n\n      describe()\n        <- rtn as String: `${host}:${port}`\n\n      default operator ?\n\n    DatabaseConfig\n      host <- \"localhost\"\n      port <- 5432\n      database <- String()\n\n      DatabaseConfig()\n        -> database as String\n        this.database: database\n\n      describe()\n        <- rtn as String: `${host}:${port}/${database}`\n\n      default operator ?\n\n  defines program\n\n    UninitialisedDemo()\n      stdout <- Stdout()\n\n      // === UNINITIALISED PROPERTIES: must be set in constructor ===\n\n      conn <- Connection(\"db.example.com\", 3306)\n      stdout.println(`Connection: ${conn.describe()}`)\n      stdout.println(`IsSet: ${conn?}`)\n\n      // === MIX: some defaults, some uninitialised ===\n\n      db <- DatabaseConfig(\"myapp\")\n      stdout.println(`Database: ${db.describe()}`)\n\n      // === Default values work without constructor args ===\n\n      stdout.println(`DB isSet: ${db?}`)","migrationContext":"Java: fields default to null/0/false, no compile-time enforcement of initialisation. Python: no field declarations, set in __init__, AttributeError if accessed before set. Rust: all fields must be initialised in struct literal, compiler enforces. Go: fields zero-valued by default (empty string, 0, nil). Kotlin: lateinit var for deferred initialisation, crashes at runtime if accessed too early. EK9: compile-time enforcement that properties are either declared with values or initialised in constructors, no null defaults.","keywords":["NullPointerException","analysis","compile","constructor","default","field","flow","initialise","lateinit","migrate","null","object-oriented","property","required","safety","uninitialised","uninitialized","value"],"primaryTopics":[],"typicalErrors":[{"error":"E08180","correct":"host as String?","incorrect":"host as String","explanation":"Properties declared without '?' and without a default value must be initialised inline. The '?' suffix marks a property as explicitly uninitialised, requiring constructor assignment. If neither '?' nor a default is provided the compiler may report E08180. See ek9 -h E08180 for details."},{"error":"E08060","correct":"this.host: host\n        this.port: port","incorrect":"this.host: host","explanation":"If a constructor does not initialise all uninitialised properties, the compiler's flow analysis detects that 'port' is never assigned any value and reports E08060 — variable declared but never initialised. Every property must be set on every code path. See ek9 -h E08060 for details."},{"error":"E08060","correct":"this.host: host\n        this.port: port","incorrect":"this.port: port","explanation":"If the constructor forgets to initialise 'host' but a method later reads it, E08060 is triggered — is/may not be initialised before use. In Java this would be a NullPointerException at runtime. EK9 catches it at compile time through flow analysis. See ek9 -h E08060 for details."},{"error":"E50001","correct":"host <- \"localhost\"","incorrect":"host <- defaultHost()","explanation":"Property initialisers must be simple values: a literal ('localhost'), a type constructor (String()), or a literal collection. Function calls and expressions are not allowed in property declarations. Java developers are used to 'private String host = computeDefault()' but EK9 requires E50001 — type must be a simple aggregate/list/dict. Use a constructor to compute values instead. See ek9 -h E50001 for details."},{"error":"E08180","correct":"host as String?","incorrect":"host as String","explanation":"Properties declared without '?' and without a default value must be initialised inline. The '?' suffix marks a property as explicitly uninitialised, requiring constructor assignment. If neither '?' nor a default is provided, E08180 is triggered. See ek9 -h E08180 for details."},{"error":"E04050","correct":"host <- \"localhost\"","incorrect":"host <- String().trim()","explanation":"Property initialisers must be simple values: a literal ('localhost'), a type constructor (String()), or a literal collection. Function calls and expressions like String().trim() are not allowed in property declarations. Use a constructor for computed values. See ek9 -h E04050 for details."}],"companions":[]}
{"id":105,"category":"Classes and OOP","question":"How does method dispatch work in EK9?","url":"https://ek9.io/qa/QA0105.html","alternatePhrasings":["What is the dispatcher keyword in EK9?","How does double dispatch work in EK9?","How do I dispatch on runtime types in EK9?","How does polymorphism work in EK9?"],"answer":"EK9 supports method dispatch via the 'as dispatcher' keyword. A dispatcher method acts as an entry point that automatically routes to the most specific overloaded method based on the runtime type of arguments.\n\nBASIC DISPATCH\nNormal method calls use compile-time types. When you need runtime type dispatch, mark the base method with 'as dispatcher':\n  render() as dispatcher\n    -> shape as Shape\n    <- rtn as String: shape.draw()\nThe runtime selects the most specific 'render' overload. The dispatcher method is the fallback when no specific handler matches.\n\nOVERLOADED HANDLERS\nOverloaded methods handle specific types:\n  render()\n    -> c as Circle\n    <- rtn as String: \"circle: \" + c.draw()\n  render()\n    -> r as Rectangle\n    <- rtn as String: \"rectangle: \" + r.draw()\n\nCOMPILE-TIME VALIDATION\nThe compiler validates dispatchers extensively:\n  E05180 — private method in super has same name as dispatcher (won't participate in dispatch)\n  E05210 — handler type not in base type hierarchy (e.g. Integer handler in Shape dispatcher)\n  E05260 — dispatcher on sealed type missing handlers for permitted types\n  E05220 — handler return type incompatible with dispatcher return type\n  E06320 — handler parameter count differs from dispatcher\n  E05170 — purity mismatch (dispatcher pure but handler not pure)\n  E07820 — multiple methods marked 'as dispatcher' with same name\n\nSEALED TYPES WITH DISPATCHERS\nWhen a trait uses 'allow only', the compiler enforces exhaustive dispatch:\n  Shape allow only Circle, Square, Triangle\nA dispatcher on Shape must have handlers for ALL permitted types (Circle, Square, AND Triangle) or E05260 is triggered. This eliminates the missing-case bugs that plague Visitor patterns.\n\nDOUBLE DISPATCH\nDispatchers can take two arguments:\n  intersect() as dispatcher\n    -> s1 as Shape, s2 as Shape\nWith overloads for specific type pairs, the runtime picks the best match. Each parameter is independently validated against its base type's hierarchy.\n\nWHEN TO USE DISPATCHER\nUse dispatchers when you need visitor-like patterns without the visitor boilerplate. They replace Java's instanceof chains, C#'s type switches, and the full Visitor pattern — with compile-time safety guarantees.\n\nSee Q96 for operators. See Q60 for function dispatching. See Q106 for traits. See Q107 for multiple traits. See Q211 for double dispatch pattern. See Q298 for sealed traits. See Q612-Q621 for dispatcher validation Q&As.","ek9Example":"defines module qa.oop.dispatch\n\n  defines trait\n\n    <?-\n      Sealed trait: only Circle, Square, Triangle can implement it.\n      Dispatchers on Shape must handle ALL three types (E05260).\n    -?>\n    Shape allow only Circle, Square, Triangle\n      area() as abstract\n        <- rtn as Float?\n\n  defines class\n\n    Circle with trait of Shape\n      override area()\n        <- rtn as Float: 3.14\n      default operator ?\n\n    Square with trait of Shape\n      override area()\n        <- rtn as Float: 1.0\n      default operator ?\n\n    Triangle with trait of Shape\n      override area()\n        <- rtn as Float: 0.5\n      default operator ?\n\n    <?-\n      Renderer dispatches on Shape.\n      Because Shape is sealed, ALL permitted types must have handlers.\n    -?>\n    Renderer\n\n      render() as dispatcher\n        -> shape as Shape\n        <- rtn as String: `unknown shape area: ${shape.area()}`\n\n      render()\n        -> circle as Circle\n        <- rtn as String: `circle area: ${circle.area()}`\n\n      render()\n        -> square as Square\n        <- rtn as String: `square area: ${square.area()}`\n\n      render()\n        -> triangle as Triangle\n        <- rtn as String: `triangle area: ${triangle.area()}`\n\n  defines program\n\n    DispatchDemo()\n      stdout <- Stdout()\n\n      renderer <- Renderer()\n\n      // === DISPATCHER: routes to most specific overload ===\n\n      shapes <- List() of Shape\n      shapes += Circle()\n      shapes += Square()\n      shapes += Triangle()\n\n      for shape in shapes\n        stdout.println(renderer.render(shape))\n\n      // === Direct calls also work ===\n\n      stdout.println(renderer.render(Circle()))\n      stdout.println(renderer.render(Triangle()))","migrationContext":"Java: Visitor pattern with accept/visit boilerplate, or instanceof chains. Python: functools.singledispatch for single argument, no multi-dispatch built-in. Rust: no runtime dispatch, pattern matching on enums instead. Go: type switch for runtime dispatch, single argument only. Kotlin: Visitor pattern or when-is chains, no built-in multi-dispatch. EK9: 'as dispatcher' keyword enables automatic runtime dispatch to most specific overload, supports multiple arguments for double dispatch, no visitor boilerplate.","keywords":["allow","dispatch","dispatcher","double","exhaustive","handler","hierarchy","method","object-oriented","only","overload","pattern","polymorphism","purity","runtime","sealed","specific","type","visitor"],"primaryTopics":[],"typicalErrors":[{"error":"E05120","correct":"override area()","incorrect":"area()","explanation":"When implementing an abstract method from Shape in a subclass like Circle, the 'override' keyword is mandatory. Omitting it triggers E05120. See ek9 -h E05120 for details."},{"error":"E50060","correct":"-> circle as Circle","incorrect":"-> circle as Integer","explanation":"A dispatcher handler's parameter type must be in the base dispatch type's hierarchy. An Integer handler in a Shape dispatcher can never be reached because Integer is not a subtype of Shape. This triggers E50060. See ek9 -h E50060 for details."},{"error":"E05030","correct":"Circle with trait of Shape","incorrect":"Circle extends Renderer","explanation":"Renderer is closed by default (not marked 'as open'). Attempting to extend a closed class triggers E05030. Only traits and classes marked 'as open' or 'as abstract' can be extended. See ek9 -h E05030 for details."},{"error":"E07075","correct":"area() as abstract\n        <- rtn as Float?","incorrect":"cachedArea <- 0.0","explanation":"Traits are stateless behaviour contracts — they cannot have properties or fields. The Shape trait should define abstract methods (like area()), not hold state. Move fields to implementing classes. See ek9 -h E07075 for details."}],"companions":[]}
{"id":106,"category":"Classes and OOP","question":"What are traits and how do I use them in EK9?","url":"https://ek9.io/qa/QA0106.html","alternatePhrasings":["What is the EK9 equivalent of Java interfaces?","How do traits differ from interfaces in EK9?","How do I define behaviour contracts in EK9?","Can traits have constructors or private methods in EK9?"],"answer":"Traits define behaviour contracts with optional default implementations. Unlike abstract classes, traits cannot hold mutable state.\n\nDEFINING A TRAIT\n  defines trait\n    Printable\n      display()\n        <- rtn as String?\nMethods can have bodies (defaults) or be abstract.\n\nIMPLEMENTING\nUse 'with trait of':\n  Product with trait of Printable, Describable\n    override display()\n      <- rtn as String: name\n\nDEFAULT METHODS\nClasses only need to override abstract methods or methods they want to customise.\n\nTRAIT VS JAVA INTERFACE\nEK9 traits support 'allow only' restriction and trait delegation beyond Java 8+ defaults.\n\nSee Q93 for class basics. See Q103 for abstract classes. See Q107 for multiple traits. See Q108 for implementing. See Q109 for composition. See Q110 for trait vs abstract. See Q264 for adapter pattern. See Q266 for delegation. See Q569 for pure methods. See Q576 for override resolution.","ek9Example":"defines module qa.oop.traits\n\n  defines trait\n\n    Printable\n      display()\n        <- rtn as String?\n\n    Describable\n      describe()\n        <- rtn as String: \"No description available\"\n\n      details() as abstract\n        <- rtn as String?\n\n    <?-\n      Formattable extends Printable — creates a trait hierarchy.\n      Classes using Formattable get Printable indirectly.\n    -?>\n    Formattable extends Printable\n      format() as abstract\n        <- rtn as String?\n\n  defines class\n\n    Product with trait of Printable, Describable\n      name <- String()\n      price <- 0.0\n\n      Product()\n        ->\n          name as String\n          price as Float\n        this.name: name\n        this.price: price\n\n      override display()\n        <- rtn as String: name\n\n      override details()\n        <- rtn as String: `${name} costs ${price}`\n\n      default operator ?\n\n    <?-\n      Report uses Formattable (which extends Printable).\n      Printable is NOT an immediate trait of Report.\n    -?>\n    Report with trait of Formattable, Describable\n      title <- String()\n\n      Report()\n        -> title as String\n        this.title: title\n\n      override display()\n        <- rtn as String: title\n\n      override details()\n        <- rtn as String: `Report: ${title}`\n\n      override format()\n        <- rtn as String: `[${title}]`\n\n      showFormatted()\n        <- rtn as String: String()\n\n        //Formattable is immediate — this is valid\n        rtn: Formattable.format()\n\n      default operator ?\n\n  defines function\n\n    <?-\n      Functions cannot access trait methods directly.\n      Must use the object reference instead.\n    -?>\n    formatItem()\n      -> item as Printable\n      <- result as String: item.display()\n\n  defines program\n\n    TraitsDemo()\n      stdout <- Stdout()\n\n      // === IMPLEMENTING A TRAIT ===\n\n      product <- Product(\"Widget\", 9.99)\n      stdout.println(`Display: ${product.display()}`)\n\n      // === DEFAULT METHOD (from Describable) ===\n\n      stdout.println(`Describe: ${product.describe()}`)\n\n      // === ABSTRACT METHOD (must override) ===\n\n      stdout.println(`Details: ${product.details()}`)\n\n      // === TRAIT CHECK ===\n\n      stdout.println(`IsSet: ${product?}`)\n\n      // === TRAIT HIERARCHY (Formattable extends Printable) ===\n\n      report <- Report(\"Q4 Sales\")\n      stdout.println(`Format: ${report.format()}`)\n      stdout.println(`ShowFormatted: ${report.showFormatted()}`)\n\n      // === FUNCTION WITH TRAIT PARAMETER ===\n\n      stdout.println(`FormatItem: ${formatItem(product)}`)","migrationContext":"Java: interfaces with defaults (8+). Python: ABCs/Protocol. Rust: traits with defaults. Go: implicit interfaces. Swift: protocols with extensions. EK9: 'with trait of', 'allow only' restriction, delegation with 'by'.","keywords":["abstract","behaviour","contract","default","define","implement","interface","method","migrate","object-oriented","printable","protocol","swift","trait"],"primaryTopics":["trait","interface","protocol","mixin"],"typicalErrors":[{"error":"E05120","correct":"override display()","incorrect":"display()","explanation":"When implementing an abstract trait method in a class, the 'override' keyword is required. Product must use 'override display()' to implement Printable's abstract method. See ek9 -h E05120 for details."},{"error":"E50060","correct":"stdout.println(`Display: ${product.display()}`)","incorrect":"stdout.println(product.toString())","explanation":"EK9 has no toString() method. Use the $ prefix operator or string interpolation for string conversion. See ek9 -h E50060 for details."},{"error":"E07075","correct":"display()\n        <- rtn as String?","incorrect":"name <- \"default\"\n\n      display()\n        <- rtn as String?","explanation":"Traits are stateless behaviour contracts and cannot have properties or fields. State belongs in implementing classes. Use abstract accessor methods in the trait instead. See ek9 -h E07075 for details."},{"error":"E07070","correct":"    Printable\n      display()\n        <- rtn as String?","incorrect":"    Printable\n      Printable()\n        -> name as String\n\n      display()\n        <- rtn as String?","explanation":"Traits cannot have constructors. They are stateless behaviour contracts with no instance creation logic. Construction belongs in implementing classes. See ek9 -h E07070 for details."},{"error":"E07270","correct":"      details() as abstract\n        <- rtn as String?","incorrect":"      private details() as abstract\n        <- rtn as String?","explanation":"Trait methods cannot have access modifiers (private, protected). All trait methods are always public. This differs from Java interfaces where private methods are allowed since Java 9. See ek9 -h E07270 for details."},{"error":"E07810","correct":"      format() as abstract\n        <- rtn as String?","incorrect":"      format() as dispatcher\n        -> item as Printable\n        <- rtn as String?","explanation":"Dispatchers are only supported in classes, not traits. The 'as dispatcher' keyword requires a concrete class context for the runtime to route overloaded methods. See ek9 -h E07810 for details."},{"error":"E06160","correct":"rtn: Formattable.format()","incorrect":"rtn: Printable.display()","explanation":"When a class declares 'with trait of Formattable' and Formattable extends Printable, only Formattable is an immediate trait. Calling Printable.display() is rejected because Printable is indirect. This prevents tight coupling to the trait hierarchy. See ek9 -h E06160 for details."},{"error":"E06170","correct":"<- result as String: item.display()","incorrect":"<- result as String: Printable.display()","explanation":"Trait method access (TraitName.method()) is only supported inside classes or dynamic classes that have that trait. Functions and programs cannot call trait methods directly. Use the object reference instead. See ek9 -h E06170 for details."}],"companions":[]}
{"id":107,"category":"Classes and OOP","question":"Can a class implement multiple traits in EK9?","url":"https://ek9.io/qa/QA0107.html","alternatePhrasings":["How do I use multiple traits on one class in EK9?","How does EK9 handle trait conflicts?","What happens when two traits have the same method?"],"answer":"Yes, in EK9, a class can implement multiple traits. When traits have conflicting method signatures, you must resolve the ambiguity by providing an explicit override.\n\nMULTIPLE TRAIT IMPLEMENTATION\nList traits separated by commas:\n  Report with trait of Printable, Exportable\n\nCONFLICT RESOLUTION\nWhen two traits define the same method, override it in the class:\n  override format()\n    <- rtn as String: Printable.format()\nYou can choose which trait's default to call using 'TraitName.method()' syntax.\n\nALLOW ONLY RESTRICTIONS\nTraits can restrict which classes may implement them:\n  Response allow only JsonResponse, XmlResponse\nOnly the listed classes can use 'with trait of Response'.\n\nAMBIGUOUS METHODS\nThe compiler detects when trait methods conflict and requires explicit resolution. This prevents silent bugs from accidental method shadowing.\n\nSee Q106 for trait basics. See Q108 for implementing traits step by step. See Q105 for dispatching with traits. See Q576 for class and trait override resolution.","ek9Example":"defines module qa.oop.multipletraits\n\n  defines trait\n\n    Serializable\n      toText()\n        <- rtn as String: \"serialized\"\n\n      override operator ? as pure\n        <- rtn as Boolean: true\n\n    Loggable\n      toText()\n        <- rtn as String: \"logged\"\n\n      logLevel()\n        <- rtn as String: \"INFO\"\n\n      override operator ? as pure\n        <- rtn as Boolean: true\n\n  defines class\n\n    Event with trait of Serializable, Loggable\n      message <- String()\n\n      Event()\n        -> message as String\n        this.message: message\n\n      override toText()\n        <- rtn as String: message\n\n      override operator ? as pure\n        <- rtn as Boolean: message?\n\n  defines program\n\n    MultipleTraitsDemo()\n      stdout <- Stdout()\n\n      // === MULTIPLE TRAITS ===\n\n      event <- Event(\"User logged in\")\n\n      // toText() resolved by explicit override\n      stdout.println(`Text: ${event.toText()}`)\n\n      // logLevel() from Loggable (no conflict)\n      stdout.println(`Level: ${event.logLevel()}`)\n\n      stdout.println(`IsSet: ${event?}`)","migrationContext":"Java: class implements multiple interfaces, default method conflicts require override with InterfaceName.super.method(). Python: multiple inheritance with MRO (Method Resolution Order), diamond problem handled by C3 linearisation. Rust: multiple trait implementations, explicit disambiguation with <Type as Trait>::method(). Go: struct satisfies multiple interfaces implicitly, no conflict possible (no defaults). Kotlin: multiple interfaces, conflicts resolved with super<InterfaceName>.method(). EK9: multiple traits with 'with trait of A, B', conflicts resolved with 'TraitName.method()' syntax, 'allow only' restricts implementors.","keywords":["abstract","allow","ambiguous","class","conflict","diamond","implement","multiple","object-oriented","only","open","override","resolve","trait","virtual"],"primaryTopics":[],"typicalErrors":[{"error":"E05120","correct":"override toText()","incorrect":"toText()","explanation":"When both Serializable and Loggable define toText(), the implementing class Event must explicitly override it to resolve the conflict. Omitting 'override' triggers E05120. See ek9 -h E05120 for details."},{"error":"E05120","correct":"override operator ? as pure","incorrect":"operator ? as pure","explanation":"The '?' operator is defined in both traits. Event must use 'override operator ?' to resolve the conflict and provide its own implementation. See ek9 -h E05120 for details."},{"error":"E06150","correct":"override toText()\n        <- rtn as String: message","incorrect":"//toText conflict not resolved","explanation":"When two traits define the same method (toText() in both Serializable and Loggable), the implementing class must provide an explicit override to resolve the conflict. Without it, the compiler cannot determine which trait's default to use. See ek9 -h E06150 for details."}],"companions":[]}
{"id":108,"category":"Classes and OOP","question":"How do I implement a trait for my class?","url":"https://ek9.io/qa/QA0108.html","alternatePhrasings":["What is the syntax for implementing a trait in EK9?","How do I add trait methods to a class in EK9?","How do I override trait default methods?"],"answer":"Note: override applies to both methods and operators (e.g., override operator ? as pure, override operator $ as pure). \n\nIn EK9, implement a trait using 'with trait of' on the class declaration. You must override all abstract methods from the trait. You can optionally override default methods.\n\nSYNTAX\nUse 'with trait of TraitName' after the class name:\n  EmailSender with trait of Sender\n    override send()\n      -> message as String\n      <- rtn as Boolean: true\n\nIMPLEMENTING REQUIRED METHODS\nAbstract trait methods must be overridden in the class:\n  defines trait\n    Sender\n      send() as abstract\n        -> message as String\n        <- rtn as Boolean?\nThe class must provide 'override send()' with a body.\n\nOVERRIDING DEFAULT METHODS\nMethods with bodies in the trait are optional to override:\n  defines trait\n    Sender\n      retry()\n        <- rtn as Integer: 3\nThe class inherits retry() returning 3, or can override it.\n\nTRAIT HIERARCHIES\nTraits can extend other traits:\n  ReliableSender with trait of Sender\n    acknowledge() as abstract\n      <- rtn as Boolean?\nA class implementing ReliableSender must implement both send() and acknowledge().\n\nSee Q106 for trait basics. See Q107 for multiple traits. See Q93 for class basics. See Q115 for dynamic classes implementing traits.","ek9Example":"defines module qa.oop.implementtrait\n\n  defines trait\n\n    Sender\n      send() as abstract\n        -> message as String\n        <- rtn as Boolean?\n\n      retry() as pure\n        <- rtn as Integer: 3\n\n    ReliableSender with trait of Sender\n      acknowledge() as abstract\n        <- rtn as Boolean?\n\n  defines class\n\n    EmailSender with trait of Sender\n      override send()\n        -> message as String\n        <- rtn as Boolean: true\n\n      default operator ?\n\n    SmtpSender with trait of ReliableSender\n      override send()\n        -> message as String\n        <- rtn as Boolean: true\n\n      override acknowledge()\n        <- rtn as Boolean: true\n\n      override retry() as pure\n        <- rtn as Integer: 5\n\n      default operator ?\n\n  defines program\n\n    ImplementTraitDemo()\n      stdout <- Stdout()\n\n      // === BASIC TRAIT IMPLEMENTATION ===\n\n      email <- EmailSender()\n      stdout.println(`Send: ${email.send(\"Hello\")}`)\n      stdout.println(`Default retry: ${email.retry()}`)\n\n      // === TRAIT HIERARCHY ===\n\n      smtp <- SmtpSender()\n      stdout.println(`Send: ${smtp.send(\"Hello\")}`)\n      stdout.println(`Acknowledge: ${smtp.acknowledge()}`)\n      stdout.println(`Override retry: ${smtp.retry()}`)","migrationContext":"Java: 'class MyClass implements MyInterface', must implement all abstract methods, @Override annotation optional but recommended. Python: class MyClass(ABC) with @abstractmethod, no enforcement until instantiation. Rust: 'impl Trait for Type' block with all required methods. Go: implicit implementation, no keyword needed, just match method signatures. Kotlin: 'class MyClass : MyInterface', override keyword required. EK9: 'ClassName with trait of TraitName', override keyword required for all overridden methods, compiler enforces abstract method implementation.","keywords":["abstract","class","default","hierarchy","implement","method","object-oriented","open","override","required","sender","syntax","trait","virtual"],"primaryTopics":[],"typicalErrors":[{"error":"E05120","correct":"override send()","incorrect":"send()","explanation":"When implementing an abstract trait method, the 'override' keyword is mandatory. EmailSender must use 'override send()' to implement Sender's abstract send method. See ek9 -h E05120 for details."},{"error":"E05120","correct":"override retry() as pure","incorrect":"retry() as pure","explanation":"When overriding a default trait method, the 'override' keyword is required. SmtpSender overrides Sender's default retry() so it must use 'override'. See ek9 -h E05120 for details."},{"error":"E07130","correct":"override send()\n        -> message as String\n        <- rtn as Boolean: true\n\n      override acknowledge()","incorrect":"//send not implemented\n\n      override acknowledge()","explanation":"SmtpSender implements ReliableSender which extends Sender. The abstract send() from Sender must be overridden. Omitting it triggers E07130 — not declared abstract but still has abstract methods. See ek9 -h E07130 for details."},{"error":"E05110","correct":"override send()\n        -> message as String\n        <- rtn as Boolean: true","incorrect":"override snd()\n        -> message as String\n        <- rtn as Boolean: true","explanation":"The 'override' keyword claims a method exists in the hierarchy, but 'snd' does not match any method in Sender. This catches typos and refactoring errors — if a trait method is renamed, all implementors are flagged. See ek9 -h E05110 for details."}],"companions":[],"oracleToolHint":{"tool":"ek9_implement","intent":"trait","description":"Oracle can generate required method stubs when implementing a trait for a class."}}
{"id":109,"category":"Classes and OOP","question":"How do I use composition instead of inheritance in EK9?","url":"https://ek9.io/qa/QA0109.html","alternatePhrasings":["How does delegation work in EK9?","What is the 'by' keyword for trait delegation in EK9?","How do I avoid inheritance with composition in EK9?"],"answer":"EK9 supports composition over inheritance through trait delegation with the 'by' keyword. A class delegates trait methods to a field, avoiding tight inheritance coupling.\n\nWHY COMPOSITION\nInheritance creates tight coupling between parent and child. Composition lets you combine behaviours flexibly through delegation. EK9's closed-by-default types encourage this approach.\n\nDELEGATION WITH BY KEYWORD\nDelegate trait methods to a field:\n  Worker with trait of Task by delegate\n    delegate as Task?\n    Worker()\n      -> delegate as Task\n      this.delegate: delegate\nAll Task methods are automatically forwarded to the delegate field.\n\nTRAIT DELEGATION PATTERN\nCombine multiple trait delegations:\n  Employee with trait of Role by role, Payroll by payroll\n    role as Role?\n    payroll as Payroll?\n\nCOMPOSITION VS INHERITANCE DECISION\nUse composition when: you need to combine multiple behaviours, swap implementations at runtime, or the relationship is 'has-a' not 'is-a'.\nUse inheritance when: there is a genuine 'is-a' relationship and you control the base class.\n\nSee Q101 for closed by default. See Q106 for traits. See Q108 for implementing traits. See Q128 for applying composition to wrap closed collection types. See Q210 for trait delegation pattern. See Q212 for composition over inheritance. See Q264 for adapter pattern using composition. See Q315 for inheritance depth limits.","ek9Example":"defines module qa.oop.composition\n\n  defines trait\n\n    Logger\n      log()\n        -> message as String\n      override operator ? as pure\n        <- rtn as Boolean: true\n\n    Formatter\n      format() as pure\n        -> content as String\n        <- rtn as String?\n      override operator ? as pure\n        <- rtn as Boolean: true\n\n  defines class\n\n    ConsoleLogger with trait of Logger\n      stdout as Stdout: Stdout()\n\n      override log()\n        -> message as String\n        stdout.println(message)\n\n      default operator ?\n\n    BracketFormatter with trait of Formatter\n      override format() as pure\n        -> content as String\n        <- rtn as String: `[${content}]`\n\n      default operator ?\n\n    Service with trait of Logger by logger, Formatter by formatter\n      logger as Logger?\n      formatter as Formatter?\n\n      default private Service()\n\n      Service()\n        ->\n          logger as Logger\n          formatter as Formatter\n        this.logger: logger\n        this.formatter: formatter\n\n      process()\n        -> content as String\n        formatted <- format(content)\n        log(formatted)\n\n      override operator ? as pure\n        <- rtn as Boolean: logger? and formatter?\n\n  defines program\n\n    CompositionDemo()\n      stdout <- Stdout()\n\n      // === COMPOSITION VIA DELEGATION ===\n\n      logger <- ConsoleLogger()\n      formatter <- BracketFormatter()\n\n      service <- Service(logger, formatter)\n      service.process(\"Hello composition\")\n      service.process(\"Delegation works\")\n\n      // === DELEGATED METHODS ALSO CALLABLE DIRECTLY ===\n\n      service.log(\"Direct log call\")\n      stdout.println(service.format(\"Direct format\"))","migrationContext":"Java: no delegation syntax, must manually write forwarding methods or use IDE generation. Python: no delegation syntax, use __getattr__ for dynamic delegation. Rust: no delegation, manual forwarding or Deref trait abuse. Go: struct embedding provides automatic method forwarding, closest to EK9 delegation. Kotlin: 'by' keyword for interface delegation, identical concept to EK9. EK9: 'with trait of T by field' delegates all trait methods to the field automatically, combinable with multiple traits.","keywords":["by","combine","composition","decouple","delegation","field","flexible","forward","inheritance","migrate","object-oriented","trait"],"primaryTopics":["composition","composition over inheritance","delegation"],"typicalErrors":[{"error":"E05120","correct":"override log()","incorrect":"log()","explanation":"When implementing an abstract or default trait method in a concrete class, the 'override' keyword is required. ConsoleLogger must use 'override log()' to implement Logger's log method. See ek9 -h E05120 for details."},{"error":"E05030","correct":"Service with trait of Logger by logger, Formatter by formatter","incorrect":"Service extends ConsoleLogger","explanation":"ConsoleLogger is closed by default (no 'as open'). Use trait delegation with 'by' instead of inheritance to compose behaviours. See ek9 -h E05030 for details."},{"error":"E07090","correct":"    Service with trait of Logger by logger, Formatter by formatter\n      logger as Logger?","incorrect":"    CompositeTrait with trait of Logger by logger\n      logger as Logger?","explanation":"Changing class name to 'CompositeTrait' makes the existing constructor named 'Service()' no longer match the class name. It becomes a regular method, and 'default' modifier on a regular method triggers E07090 — 'default' is only valid for constructors. See ek9 -h E07090 for details."}],"companions":[]}
{"id":110,"category":"Classes and OOP","question":"What is the difference between a trait and an abstract class?","url":"https://ek9.io/qa/QA0110.html","alternatePhrasings":["When should I use a trait vs an abstract class in EK9?","How do traits compare to abstract classes in EK9?","Should I use a trait or an abstract base class?"],"answer":"In EK9, traits and abstract classes both define contracts, but differ in state, multiplicity, and purpose. Traits provide pure behaviour. Abstract classes provide shared state and partial implementation.\n\nKEY DIFFERENCES\nState: Abstract classes can hold properties. Traits cannot hold mutable state.\nMultiple: A class can implement many traits but extend only one abstract class.\nPurpose: Traits define what a type CAN DO. Abstract classes define what a type IS.\n\nWHEN TO USE EACH\nUse a trait when: you want behaviour shared across unrelated classes, you need multiple implementations, or you want delegation support.\nUse an abstract class when: you need shared state (properties), you need constructors, or you have a genuine type hierarchy.\n\nCAN YOU COMBINE THEM\nYes. A class can extend an abstract class AND implement traits:\n  Car extends Vehicle with trait of Insurable, Trackable\nThis combines the state from Vehicle with behaviours from Insurable and Trackable.\n\nDECISION GUIDE\nAsk: does the contract need state? If yes, abstract class. Does the contract need to be mixed into multiple unrelated types? If yes, trait.\n\nSee Q103 for abstract classes. See Q106 for traits. See Q119 for constructs overview.","ek9Example":"defines module qa.oop.traitvsabstract\n\n  defines trait\n\n    Insurable\n      premium() as pure\n        <- rtn as Float: 100.0\n\n      override operator ? as pure\n        <- rtn as Boolean: true\n\n    Trackable\n      location() as abstract\n        <- rtn as String?\n\n      override operator ? as pure\n        <- rtn as Boolean: true\n\n  defines class\n\n    Vehicle as abstract\n      make <- String()\n      year <- Integer()\n\n      Vehicle()\n        ->\n          make as String\n          year as Integer\n        this.make: make\n        this.year: year\n\n      describe()\n        <- rtn as String: `${make} (${year})`\n\n      speed() as abstract\n        <- rtn as Float?\n\n      default operator ?\n\n    Car extends Vehicle with trait of Insurable, Trackable\n      topSpeed <- 0.0\n\n      Car()\n        ->\n          make as String\n          year as Integer\n          topSpeed as Float\n        super(make, year)\n        this.topSpeed: topSpeed\n\n      override speed()\n        <- rtn as Float: topSpeed\n\n      override premium() as pure\n        <- rtn as Float: 200.0\n\n      override location()\n        <- rtn as String: \"garage\"\n\n      default operator ?\n\n    Truck extends Vehicle\n      payload <- 0.0\n\n      Truck()\n        ->\n          make as String\n          year as Integer\n          payload as Float\n        super(make, year)\n        this.payload: payload\n\n      override speed()\n        <- rtn as Float: 80.0\n\n      default operator ?\n\n  defines program\n\n    TraitVsAbstractDemo()\n      stdout <- Stdout()\n\n      // === ABSTRACT CLASS: shared state ===\n\n      car <- Car(\"Toyota\", 2024, 130.0)\n      stdout.println(`Car: ${car.describe()}, speed: ${car.speed()}`)\n\n      // === TRAIT: behaviour contract ===\n\n      stdout.println(`Premium: ${car.premium()}`)\n      stdout.println(`Location: ${car.location()}`)\n\n      // === Abstract without trait ===\n\n      truck <- Truck(\"Ford\", 2023, 5000.0)\n      stdout.println(`Truck: ${truck.describe()}, speed: ${truck.speed()}`)","migrationContext":"Java: interfaces (no state) vs abstract classes (state + partial impl), single inheritance for classes. Python: ABCs for abstract, mixins for trait-like behaviour, multiple inheritance allowed. Rust: only traits exist, no abstract classes, traits can have default methods. Go: only interfaces, no abstract classes, no state in interfaces. Kotlin: interfaces with properties (no backing fields) vs abstract classes, similar trade-offs. EK9: traits for behaviour contracts (no mutable state), abstract classes for shared state and hierarchy, combinable on same class.","keywords":["abstract","behaviour","class","combine","decision","difference","hierarchy","multiple","object-oriented","oop","state","trait"],"primaryTopics":[],"typicalErrors":[{"error":"E05120","correct":"override speed()","incorrect":"speed()","explanation":"When implementing an abstract method from Vehicle, both Car and Truck must use 'override speed()'. Omitting 'override' triggers E05120. See ek9 -h E05120 for details."},{"error":"E05120","correct":"override premium() as pure","incorrect":"premium() as pure","explanation":"When overriding a default trait method from Insurable, the 'override' keyword is mandatory. Car must use 'override premium()' to provide its custom implementation. See ek9 -h E05120 for details."},{"error":"E50060","correct":"stdout.println(`Car: ${car.describe()}, speed: ${car.speed()}`)","incorrect":"stdout.println(car.toString())","explanation":"EK9 has no toString() method. Use the $ prefix operator or string interpolation for string conversion. See ek9 -h E50060 for details."},{"error":"E50060","correct":"stdout.println(`Truck: ${truck.describe()}, speed: ${truck.speed()}`)","incorrect":"stdout.println(truck.getSpeed())","explanation":"The method is speed(), not getSpeed(). EK9 uses descriptive method names without Java-style 'get' prefix. See ek9 -h E50060 for details."}],"companions":[]}
{"id":111,"category":"Classes and OOP","question":"What are components in EK9?","url":"https://ek9.io/qa/QA0111.html","alternatePhrasings":["How does dependency injection work in EK9?","What is the component construct in EK9?","How do I use IoC containers in EK9?"],"answer":"Components in EK9 are constructs designed for dependency injection (DI). They are registered in application definitions and injected into programs and other components using the '!' suffix.\n\nCOMPONENT BASICS\nDefine a component under 'defines component':\n  defines component\n    Repository as abstract\n      findAll() as abstract\n        <- rtn as List of String?\n      default operator ?\n\nABSTRACT COMPONENTS\nDefine abstract components as contracts. Concrete implementations extend them:\n  InMemoryRepository extends Repository\n    override findAll()\n      <- rtn as List of String: [\"item1\", \"item2\"]\n\nINJECTION\nUse the '!' suffix to inject a registered component:\n  repository as Repository!\nThe runtime resolves which concrete implementation was registered.\n\nAPPLICATION REGISTRATION\nRegister components in an application definition:\n  defines application\n    MyApp\n      register InMemoryRepository() as Repository\n\nPROGRAM WITH APPLICATION\nLink a program to an application for injection:\n  Program1 with application of MyApp\n    repository as Repository!\n    items <- repository.findAll()\n\nCOMPONENT VS CLASS\nComponents are for DI-managed singletons and services. Classes are for regular objects created directly.\n\nSee Q93 for class basics. See Q103 for abstract classes. See Q112 for services. See Q113 for text constructs. See Q114 for aspects. See Q119 for constructs overview. See Q227 for compile-time DI validation. See Q228 for registration ordering. See Q231 for program-application linking.\n\nSee Q324 for @Autowired equivalent. See Q325 for @Component equivalent.\n\nSee Q667 for abstract injection only. See Q668 for injectable contexts. See Q672 for program-application link.","ek9Example":"defines module qa.oop.components\n\n  defines component\n\n    Repository as abstract\n      findAll() as abstract\n        <- rtn as List of String?\n\n      default operator ?\n\n    InMemoryRepository extends Repository\n      override findAll()\n        <- rtn <- List() of String\n        rtn += \"Alice\"\n        rtn += \"Bob\"\n        rtn += \"Charlie\"\n\n      default operator ?\n\n  defines application\n\n    MyApp\n      register InMemoryRepository() as Repository\n\n  defines program\n\n    ComponentDemo() with application of MyApp\n      stdout <- Stdout()\n\n      // === INJECTION with ! suffix ===\n\n      repository as Repository!\n\n      // === USE INJECTED COMPONENT ===\n\n      items <- repository.findAll()\n      for item in items\n        stdout.println(`Item: ${item}`)\n\n      stdout.println(`Total: ${length items}`)","migrationContext":"Java: Spring @Component/@Service with @Autowired injection, CDI @Inject. Python: no built-in DI, frameworks like dependency-injector or FastAPI Depends. Rust: no built-in DI, manual wiring or shaku crate. Go: no built-in DI, wire or dig packages. Kotlin: Koin or Dagger for DI, no language-level support. EK9: 'defines component' is a language-level construct, 'register X as Y' in application, '!' suffix for injection, compile-time validated DI.","keywords":["abstract","application","component","dependency","inject","injection","inversion","ioc","object-oriented","register","service"],"primaryTopics":["component","dependency injection component"],"typicalErrors":[{"error":"E05120","correct":"override findAll()","incorrect":"findAll()","explanation":"When implementing an abstract method from the parent component, the 'override' keyword is mandatory. InMemoryRepository must use 'override findAll()'. See ek9 -h E05120 for details."},{"error":"E50040","correct":"Repository as abstract","incorrect":"Repository","explanation":"Without 'as abstract', Repository contains abstract method 'findAll()' but the component itself is not marked abstract. The compiler reports E50040 — cannot be abstract in a non-abstract construct. See ek9 -h E50040 for details."},{"error":"E07250","correct":"override findAll()","incorrect":"override protected findAll()","explanation":"Component methods cannot use the 'protected' access modifier. Components are DI-managed singletons without subclass hierarchies, so 'protected' is meaningless. Use 'private' or 'public' instead. See ek9 -h E07250 for details."},{"error":"E08150","correct":"repository as Repository!","incorrect":"repository as InMemoryRepository!","explanation":"Only abstract components can be injected. Injecting a concrete component directly bypasses the DI contract — always inject the abstract type and register the concrete implementation in the application. See ek9 -h E08150 for details."},{"error":"E50010","correct":"repository as Repository!","incorrect":"processor as DataProcessor!","explanation":"Dependency injection with '!' only works with components. DataProcessor is a class, not a component — classes are constructed directly, not injected. Use 'defines component' for injectable types. See ek9 -h E50010 for details."},{"error":"E50020","correct":"defines component","incorrect":"defines class","explanation":"Changing 'defines component' to 'defines class' makes Repository a class instead of a component. The register statement requires component types, so the compiler rejects it with E50020 — incompatible genus. Components and classes are distinct constructs in EK9. See ek9 -h E50020 for details."},{"error":"E50080","correct":"register InMemoryRepository() as Repository","incorrect":"register Repository() as Repository","explanation":"Repository is declared 'as abstract' and cannot be instantiated directly. The register statement must use a concrete implementation (InMemoryRepository), not the abstract base type. See ek9 -h E50080 for details."}],"companions":[]}
{"id":112,"category":"Classes and OOP","question":"How do services work in EK9 for REST and HTTP?","url":"https://ek9.io/qa/QA0112.html","alternatePhrasings":["How do I create a REST service in EK9?","What is the service construct in EK9?","How does EK9 map HTTP methods to operators?"],"answer":"EK9 has a built-in service construct for REST/HTTP endpoints. Services map HTTP methods to operators and named methods, with URI binding and parameter extraction built into the language.\n\nSERVICE CONSTRUCT\nDefine a service with its URI:\n  defines service\n    Items :/items\n\nURI MAPPING\nThe service name maps to a base URI path. Methods map to sub-paths:\n  listAll() :/             maps to GET /items\n  byId() as GET for :/{id}  maps to GET /items/{id}\n\nHTTP METHOD MAPPING\nEK9 maps operators to HTTP verbs semantically:\n  operator += :/           POST (add to collection)\n  operator -= :/{id}       DELETE (remove from collection)\n  operator :~: :/{id}      PATCH (merge/partial update)\n  operator :^: :/{id}      PUT (replace)\n  Named methods default to GET.\n\nPATH AND CONTENT BINDING\n  -> id as String                         PATH parameter (assumed)\n  -> content as String :=: CONTENT        request body\n  -> request as HTTPRequest :=: REQUEST   full request object\n\nHTTPRESPONSE TRAIT\nService methods return HTTPResponse. Use dynamic classes to implement it inline with trait delegation.\n\nAPPLICATION REGISTRATION\nRegister the service in an application:\n  register Items()\n\nSee Q111 for components. See Q106 for traits. See Q96 for operator semantics. See Q114 for aspects. See Q199 for REST GET endpoints. See Q200 for CRUD operator mapping. See Q201 for building HTTPResponse. See Q203 for application wiring.\n\nSee Q325 for @Component/@Service equivalent.","ek9Example":"defines module qa.oop.services\n\n  defines text for \"en\"\n\n    ServiceContent\n      welcome()\n        \"Welcome to the EK9 Service\"\n\n  defines service\n\n    Info :/info open\n\n      welcome() as GET for :/welcome\n        <- response as HTTPResponse: () with trait HTTPResponse\n          content <- ServiceContent(\"en\")\n\n          override cacheControl() as pure\n            <- rtn as String: \"no-store\"\n          override contentType() as pure\n            <- rtn as String: \"text/plain\"\n          override contentLanguage() as pure\n            <- rtn as String: \"en\"\n          override content()\n            <- rtn as String: content.welcome()\n          override status() as pure\n            <- rtn as Integer: 200\n          default operator ?\n\n  defines application\n\n    ServiceApp\n      register Info()\n\n  defines program\n\n    ServiceDemo()\n      stdout <- Stdout()\n\n      // === SERVICE CONSTRUCT ===\n      // Services are defined with 'defines service'\n      // and registered in applications\n\n      stdout.println(\"Service registered in application\")\n      stdout.println(\"HTTP methods map to operators:\")\n      stdout.println(\"  += is POST, -= is DELETE\")\n      stdout.println(\"  :~: is PATCH, :^: is PUT\")\n      stdout.println(\"  Named methods default to GET\")","migrationContext":"Java: JAX-RS @GET/@POST annotations or Spring @RestController with @RequestMapping, annotation-driven. Python: Flask/Django with @app.route decorators, framework-dependent. Rust: actix-web or axum with handler functions and extractors. Go: net/http handlers with manual routing or gorilla/mux. Kotlin: Ktor routing DSL or Spring Boot annotations. EK9: language-level 'defines service' with operator-to-HTTP mapping, URI binding in declaration, parameter binding with PATH/CONTENT/REQUEST keywords.","keywords":["delete","endpoint","get","http","object-oriented","operator","patch","post","put","rest","service","uri"],"primaryTopics":["service","REST service","HTTP service","web service"],"typicalErrors":[{"error":"E05120","correct":"override content()","incorrect":"content()","explanation":"When implementing HTTPResponse trait methods in a dynamic class, the 'override' keyword is required for each method. Omitting it triggers E05120. See ek9 -h E05120 for details."},{"error":"E05110","correct":"override status() as pure\n            <- rtn as Integer: 200","incorrect":"override getStatus() as pure\n            <- rtn as Integer: 200","explanation":"The HTTPResponse trait method is 'status()', not 'getStatus()'. Using 'override' with a non-existent method name triggers E05110 — false override claim. EK9 does not use Java-style getter naming. See ek9 -h E05110 for details."},{"error":"E50060","correct":"content.welcome()","incorrect":"content.toString()","explanation":"EK9 does not have a toString() method. Use the $ operator or call the appropriate method directly. See ek9 -h E50060 for details."}],"companions":[]}
{"id":113,"category":"Classes and OOP","question":"How does the text construct work for internationalization?","url":"https://ek9.io/qa/QA0113.html","alternatePhrasings":["How do I do i18n in EK9?","How does EK9 handle multiple languages and locales?","What is the text construct in EK9?"],"answer":"EK9 has a built-in 'text' construct for internationalization. You define text components per locale, and the compiler validates that all locales have the same methods.\n\nTEXT CONSTRUCT\nDefine text for a specific locale:\n  defines text for \"en\"\n    Greetings\n      hello\n        \"Hello!\"\n      welcomeUser\n        -> name as String\n        `Welcome, ${name}!`\n\nTEXT METHODS\nMethods can be simple (no parameters) or parameterised with typed arguments:\n  farewell\n    -> name as String\n    `Goodbye, ${name}!`\nParameters are interpolated with '${variable}' syntax.\n\nMULTIPLE LOCALES\nDefine the same text component for each locale:\n  defines text for \"fr\"\n    Greetings\n      hello\n        \"Bonjour!\"\n      welcomeUser\n        -> name as String\n        `Bienvenue, ${name}!`\n\nCOMPILER VALIDATION\nThe compiler checks that all locales define exactly the same methods with the same parameter types. Missing or mismatched methods are compile-time errors.\n\nRUNTIME LOCALE SELECTION\nCreate the text component with a locale string:\n  greetings <- Greetings(\"en\")\n  greetings.hello()\n\nSee Q37 for strings. See Q43 for escape and interpolation. See Q111 for components. See Q119 for constructs overview.","ek9Example":"defines module qa.oop.textconstruct\n\n  defines text for \"en\"\n\n    Greetings\n      hello\n        \"Hello!\"\n      welcomeUser\n        -> name as String\n        `Welcome, ${name}!`\n      farewell\n        -> name as String\n        `Goodbye, ${name}!`\n\n  defines text for \"fr\"\n\n    Greetings\n      hello\n        \"Bonjour!\"\n      welcomeUser\n        -> name as String\n        `Bienvenue, ${name}!`\n      farewell\n        -> name as String\n        `Au revoir, ${name}!`\n\n  defines program\n\n    TextDemo()\n      stdout <- Stdout()\n\n      // === ENGLISH TEXT ===\n\n      testName <- \"Steve\"\n\n      en <- Greetings(\"en\")\n      stdout.println(en.hello())\n      stdout.println(en.welcomeUser(testName))\n      stdout.println(en.farewell(testName))\n\n      // === FRENCH TEXT ===\n\n      fr <- Greetings(\"fr\")\n      stdout.println(fr.hello())\n      stdout.println(fr.welcomeUser(testName))\n      stdout.println(fr.farewell(testName))","migrationContext":"Java: ResourceBundle with .properties files, MessageFormat for parameters, no compile-time validation across locales. Python: gettext with .po files, string formatting, no compile-time locale validation. Rust: fluent-rs or gettext-rs crates, external file based. Go: go-i18n package, JSON/TOML message files, no compile-time cross-locale validation. Kotlin: same as Java ResourceBundle. EK9: language-level 'defines text for locale' construct, typed parameters with interpolation, compiler validates all locales have matching method signatures.","keywords":["compiler","constant","construct","i18n","internationalization","interpolation","language","locale","object-oriented","text","translation","validation"],"primaryTopics":["text","internationalization","i18n"],"typicalErrors":[{"error":"E07150","correct":"farewell\n        -> name as String\n        `Au revoir, ${name}!`","incorrect":"bonjour\n        -> name as String\n        `Bonjour encore, ${name}!`","explanation":"All locale variants of a text component must define exactly the same methods. Renaming 'farewell' to 'bonjour' in the fr variant means fr no longer has a 'farewell' method matching en, triggering E07150. See ek9 -h E07150 for details."},{"error":"E07155","correct":"defines text for \"fr\"","incorrect":"defines text for \"en\"","explanation":"Each text type name must appear only once per locale. Changing fr to en creates a second 'Greetings' definition for locale en, triggering E07155 — duplicate text type for locale. See ek9 -h E07155 for details."},{"error":"E07900","correct":"defines text for \"en\"","incorrect":"defines text for \"EN\"","explanation":"Language codes must follow the pattern [a-z]+(_[A-Z]+)?. Uppercase base 'EN' should be lowercase 'en'. Use 'en_GB' not 'en-GB' or 'en_gb'. See ek9 -h E07900 for details."}],"companions":[]}
{"id":114,"category":"Classes and OOP","question":"How do aspects work with components for AOP?","url":"https://ek9.io/qa/QA0114.html","alternatePhrasings":["How does aspect-oriented programming work in EK9?","How do I add logging or timing to components in EK9?","What is the Aspect class in EK9?"],"answer":"EK9 supports Aspect-Oriented Programming through the Aspect base class and application registration. Aspects wrap component calls with before and after advice.\n\nASPECT CLASS\nCreate an aspect by extending the built-in Aspect class:\n  LoggingAspect extends Aspect\n    override beforeAdvice()\n      -> joinPoint as JoinPoint\n      <- rtn as PreparedMetaData: PreparedMetaData(joinPoint)\n    override afterAdvice()\n      -> preparedMetaData as PreparedMetaData\n\nBEFORE AND AFTER ADVICE\nbeforeAdvice() runs before each method call. afterAdvice() runs after. The JoinPoint provides the component name and method name.\n\nJOINPOINT\nThe JoinPoint parameter gives execution context:\n  joinPoint.componentName()   the component being called\n  joinPoint.methodName()      the method being called\n\nREGISTERING WITH ASPECTS\nUse 'with aspect of' in application registration:\n  register Solution1() as Config with aspect of LoggingAspect(), TimerAspect()\nMultiple aspects chain in declaration order.\n\nCHAINING MULTIPLE ASPECTS\nMultiple aspects wrap in order: the first listed runs outermost. Each aspect's beforeAdvice runs before the next, and afterAdvice runs in reverse order.\n\nSee Q111 for components. See Q112 for services. See Q102 for 'as open'. See Q119 for constructs overview. See Q227 for compile-time DI validation.\n\nSee Q337 for transaction aspects.","ek9Example":"defines module qa.oop.aspects\n\n  defines component\n\n    Config as abstract\n      getValue() as abstract\n        <- rtn as String?\n      default operator ?\n\n    ProductionConfig extends Config\n      override getValue()\n        <- rtn as String: \"production-database\"\n      default operator ?\n\n  defines class\n\n    SimpleAspect extends Aspect\n      label <- String()\n\n      SimpleAspect()\n        -> label as String\n        this.label: label\n\n      override beforeAdvice()\n        -> joinPoint as JoinPoint\n        <- rtn as PreparedMetaData: PreparedMetaData(joinPoint)\n        Stdout().println(`${label} BEFORE: ${joinPoint.componentName()}.${joinPoint.methodName()}`)\n\n      override afterAdvice()\n        -> preparedMetaData as PreparedMetaData\n        joinPoint <- preparedMetaData.joinPoint()\n        Stdout().println(`${label} AFTER: ${joinPoint.componentName()}.${joinPoint.methodName()}`)\n\n      default operator ?\n\n  defines application\n\n    AspectApp\n      register ProductionConfig() as Config with aspect of SimpleAspect(\"LOG\")\n\n  defines program\n\n    AspectsDemo() with application of AspectApp\n      stdout <- Stdout()\n\n      // === INJECTION with aspects applied ===\n\n      config as Config!\n      stdout.println(`Value: ${config.getValue()}`)","migrationContext":"Java: Spring AOP with @Before/@After annotations and proxy-based weaving, AspectJ for compile-time weaving. Python: decorators for simple AOP, no JoinPoint concept built-in. Rust: no built-in AOP, procedural macros for compile-time code generation. Go: no built-in AOP, middleware pattern for HTTP, manual wrapping. Kotlin: Spring AOP same as Java, no language-level AOP. EK9: language-level Aspect base class, beforeAdvice/afterAdvice methods, JoinPoint for context, 'register with aspect of' syntax in application definitions.","keywords":["advice","after","aop","aspect","before","component","cross-cutting","joinpoint","logging","object-oriented","register","timing","weave"],"primaryTopics":["aspect","AOP","cross cutting"],"typicalErrors":[{"error":"E05120","correct":"override beforeAdvice()","incorrect":"beforeAdvice()","explanation":"When implementing Aspect's abstract methods beforeAdvice() and afterAdvice(), the 'override' keyword is mandatory. Omitting it triggers E05120. See ek9 -h E05120 for details."},{"error":"E05120","correct":"override getValue()","incorrect":"getValue()","explanation":"ProductionConfig must use 'override' when implementing Config's abstract method getValue(). See ek9 -h E05120 for details."},{"error":"E50001","correct":"register ProductionConfig() as Config with aspect of SimpleAspect(\"LOG\")","incorrect":"register ProductionConfig() as Config with aspect of NonExistentAspect()","explanation":"The aspect type used in 'with aspect of' must be a defined type. If the type does not exist, E50001 is triggered — not resolved. Check spelling and ensure the aspect class is defined. See ek9 -h E50001 for details."},{"error":"E50020","correct":"defines class","incorrect":"defines component","explanation":"Aspect is a built-in class, not a component. Defining an aspect subclass inside 'defines component' triggers E50020 because components cannot extend classes — incompatible genus. Aspect subclasses belong in 'defines class'. See ek9 -h E50020 for details."},{"error":"E50080","correct":"register ProductionConfig() as Config","incorrect":"register Config() as Config","explanation":"Config is declared 'as abstract' and cannot be instantiated directly. The register statement must use a concrete implementation (ProductionConfig), not the abstract base type. See ek9 -h E50080 for details."}],"companions":[]}
{"id":115,"category":"Classes and OOP","question":"How do dynamic classes work in EK9?","url":"https://ek9.io/qa/QA0115.html","alternatePhrasings":["How do I create anonymous classes in EK9?","What are inline trait implementations in EK9?","How do I capture variables in a dynamic class?","How do I implement a trait inline without a named class?"],"answer":"Dynamic classes in EK9 are anonymous inline classes that implement traits. They capture variables from the enclosing scope, similar to dynamic functions but for trait implementations.\n\nDYNAMIC CLASS BASICS\nCreate an anonymous class inline using '() with trait of':\n  response <- () with trait of HTTPResponse\n    override content() as pure\n      <- rtn as String: \"hello\"\n    default operator ?\n\nCAPTURE VARIABLES\nCapture values from the enclosing scope. Any type can be captured — String, Boolean, Integer, or any other type:\n  handler <- (message: msg) with trait of Handler\n    override handle()\n      <- rtn as String: message\n    default operator ?\n\nMULTIPLE CAPTURES\nCapture multiple variables of different types in a single dynamic class:\n  logger <- (logging: enableLog, prefix: tag) with trait of Handler\n    override handle()\n      <- rtn as String: `[${prefix}] logging=${logging}`\n    default operator ?\n\nCAPTURE FIXED VALUES\nLiteral values and function returns must use named captures:\n  configured <- (maxRetries: 3, label: \"RETRY\") with trait of Handler\nSimple variable identifiers can be unnamed, but expressions must be named.\n\nIMPLEMENTING TRAITS INLINE\nDynamic classes can implement any trait. Override abstract methods and provide a default operator. The trait delegation pattern also works:\n  (delegate: baseHandler) with trait of Handler by delegate\n\nWHEN TO USE DYNAMIC CLASSES\nUse dynamic classes for one-off trait implementations, especially in service responses, callbacks, and strategy patterns where a full named class would be excessive.\n\nSee Q52 for dynamic functions. See Q106 for traits. See Q108 for implementing traits. See Q112 for services using dynamic classes. See Q233 for dynamic class dependency injection. See Q262 for observer pattern with dynamic function listeners. See Q263 for factory pattern with dynamic class products.","ek9Example":"defines module qa.oop.dynamicclasses\n\n  defines trait\n\n    Handler\n      handle() as abstract\n        <- rtn as String?\n\n      override operator ? as pure\n        <- rtn as Boolean: true\n\n    Transformer\n      transform() as abstract\n        -> input as String\n        <- rtn as String?\n\n      override operator ? as pure\n        <- rtn as Boolean: true\n\n  defines class\n\n    DefaultHandler with trait of Handler\n      override handle()\n        <- rtn as String: \"default\"\n      default operator ?\n\n  defines function\n\n    getPrefix()\n      <- rtn as String: \"AUTO\"\n\n  defines program\n\n    DynamicClassDemo()\n      stdout <- Stdout()\n\n      // === DYNAMIC CLASS: anonymous trait implementation ===\n\n      greeting <- () with trait of Handler\n        override handle()\n          <- rtn as String: \"Hello from dynamic class\"\n        default operator ?\n\n      stdout.println(greeting.handle())\n\n      // === CAPTURE: single String variable ===\n\n      msg <- \"Captured message\"\n      captured <- (message: msg) with trait of Handler\n        override handle()\n          <- rtn as String: message\n        default operator ?\n\n      stdout.println(captured.handle())\n\n      // === CAPTURE: single Boolean variable ===\n\n      verbose <- true\n      detailed <- (showDetail: verbose) with trait of Handler\n        override handle()\n          <- rtn as String: `Detail: ${showDetail}`\n        default operator ?\n\n      stdout.println(detailed.handle())\n\n      // === CAPTURE: multiple variables (Boolean + String) ===\n\n      enableLog <- true\n      logTag <- \"LOG\"\n      logger <- (logging: enableLog, prefix: logTag) with trait of Handler\n        override handle()\n          <- rtn as String: `[${prefix}] logging=${logging}`\n        default operator ?\n\n      stdout.println(logger.handle())\n\n      // === CAPTURE: fixed literal values ===\n\n      configured <- (maxRetries: 3, label: \"RETRY\") with trait of Handler\n        override handle()\n          <- rtn as String: `${label}: max ${maxRetries}`\n        default operator ?\n\n      stdout.println(configured.handle())\n\n      // === CAPTURE: function return value ===\n\n      autoPrefix <- getPrefix()\n      auto <- (prefix: autoPrefix) with trait of Handler\n        override handle()\n          <- rtn as String: `[${prefix}] auto-configured`\n        default operator ?\n\n      stdout.println(auto.handle())\n\n      // === CAPTURE: Boolean with Transformer trait ===\n\n      shouldUpper <- true\n      transformer <- (upper: shouldUpper) with trait of Transformer\n        override transform()\n          -> input as String\n          <- rtn as String: `upper=${upper} input=${input}`\n        default operator ?\n\n      stdout.println(transformer.transform(\"hello\"))\n\n      // === DELEGATION ===\n\n      base <- DefaultHandler()\n      delegated <- (delegate: base) with trait of Handler by delegate\n\n      stdout.println(delegated.handle())","migrationContext":"Java: anonymous inner classes with 'new Interface() { ... }', captures effectively final variables from enclosing scope. Python: no anonymous classes, use lambdas or nested classes, closures capture by reference. Rust: closures implement Fn traits with move semantics, no anonymous struct implementations. Go: no anonymous interface implementations, closures capture variables. Kotlin: object expressions with 'object : Interface { ... }', captures from enclosing scope automatically. EK9: '() with trait of T' creates inline class with explicit named captures (name: value), supports any type including Boolean and Integer, delegation support via 'by', concise syntax for one-off implementations.","keywords":["anonymous","callback","capture","class","closure","delegation","dynamic","implement","inline","object-oriented","scope","trait"],"primaryTopics":["dynamic class","anonymous class"],"typicalErrors":[{"error":"E05120","correct":"override handle()","incorrect":"handle()","explanation":"Dynamic classes implementing a trait must use 'override' for abstract trait methods. The inline implementation of Handler's handle() method requires 'override'. See ek9 -h E05120 for details."},{"error":"E07140","correct":"override handle()\n          <- rtn as String: \"Hello from dynamic class\"\n        default operator ?","incorrect":"default operator ?","explanation":"A dynamic class implementing Handler must override all abstract methods. Omitting handle() leaves the abstract method unimplemented, triggering E07140. See ek9 -h E07140 for details."},{"error":"E50040","correct":"override handle()\n          <- rtn as String: message","incorrect":"badMethod() as abstract\n          <- rtn as String?","explanation":"Dynamic classes are concrete inline implementations — they cannot contain abstract methods. Declaring a method 'as abstract' inside a dynamic class triggers E50040. See ek9 -h E50040 for details."},{"error":"E50020","correct":"() with trait of Handler","incorrect":"() with trait of DefaultHandler","explanation":"Dynamic classes use 'with trait of' to implement traits. DefaultHandler is a class, not a trait — using it triggers E50020 because the genus is incompatible. Only traits can be used with dynamic class syntax. See ek9 -h E50020 for details."},{"error":"E06240","correct":"(logging: enableLog, prefix: logTag) with trait of Handler","incorrect":"(logging: enableLog, logTag) with trait of Handler","explanation":"When capturing variables in a dynamic class, either all captures must be named or all must be unnamed. Mixing named captures (logging: enableLog) with unnamed captures (logTag) triggers E06240. See ek9 -h E06240 for details."},{"error":"E06230","correct":"(maxRetries: 3, label: \"RETRY\") with trait of Handler","incorrect":"(3, \"RETRY\") with trait of Handler","explanation":"Literal values and expressions must use named captures. Unnamed captures only work for simple variable identifiers. Capturing literal '3' or '\"RETRY\"' without naming triggers E06230. See ek9 -h E06230 for details."},{"error":"E02040","correct":"(logging: enableLog, prefix: logTag) with trait of Handler","incorrect":"(logging: enableLog, logging: logTag) with trait of Handler","explanation":"Each capture field name must be unique within a dynamic class. Using 'logging' as the name for two different captures creates duplicate fields, triggering E02040. See ek9 -h E02040 for details."}],"companions":[]}
{"id":116,"category":"Classes and OOP","question":"What does 'default operator' generate?","url":"https://ek9.io/qa/QA0116.html","alternatePhrasings":["Which operators does 'default operator' create automatically?","How does auto-generated operator work in EK9?","What is the difference between 'default operator' and custom operators?"],"answer":"In EK9, the 'default operator' keyword generates standard operators automatically based on a type's fields. It provides equality, comparison, string conversion, hashing, and isSet checking.\n\nWHAT IT GENERATES\nThe following operators are auto-generated:\n  ==   equality (field-by-field comparison)\n  <>   inequality (negation of ==)\n  <=>  comparison (returns Integer, field-by-field ordering)\n  $    string conversion (concatenation of field strings)\n  #?   hashcode (combined hash of all fields)\n  ?    isSet (true when all fields are set)\n\nHOW IT WORKS\nFor each field in declaration order, the generated operator compares, concatenates, or hashes. Comparison uses the first field as primary sort, second as secondary, etc.\n\nWHEN TO USE IT\nUse 'default operator' when field-by-field behaviour is correct. This covers most value types, records, and simple classes.\n\nWHEN NOT TO USE IT\nOverride specific operators when you need custom logic: computed equality, selective comparison, custom string formatting, or partial isSet checking.\n\nRECORD VS CLASS DEFAULTS\nBoth records and classes support 'default operator'. The generated behaviour is identical, but records expose fields publicly while classes keep them private.\n\nSee Q96 for custom operators. See Q93 for class basics. See Q97 for records. See Q98 for record operators. See Q99 for enumeration auto-operators. See Q238 for the complete fixed operator set. See Q242 for conversion operators. See Q245 for implementing a complete custom type.","ek9Example":"defines module qa.oop.defaultoperator\n\n  defines class\n\n    Colour\n      red <- Integer()\n      green <- Integer()\n      blue <- Integer()\n\n      Colour()\n        ->\n          red as Integer\n          green as Integer\n          blue as Integer\n        this.red: red\n        this.green: green\n        this.blue: blue\n\n      default operator\n\n    //Demonstrates individual default operator\n    Weight\n      grams <- Float()\n\n      Weight()\n        -> grams as Float\n        this.grams: grams\n\n      default operator ==\n      default operator <>\n      default operator <=>\n      default operator $\n      default operator #?\n      default operator ?\n\n  defines trait\n\n    Displayable\n      display() as pure abstract\n        <- rtn as String?\n\n      override operator ? as pure\n        <- rtn as Boolean: true\n\n  defines record\n\n    Address\n      street <- String()\n      city <- String()\n\n      Address()\n        ->\n          street as String\n          city as String\n        this.street: street\n        this.city: city\n\n      default operator\n\n  defines program\n\n    DefaultOperatorDemo()\n      stdout <- Stdout()\n\n      // === EQUALITY (==) and INEQUALITY (<>) ===\n\n      red1 <- Colour(255, 0, 0)\n      red2 <- Colour(255, 0, 0)\n      blue <- Colour(0, 0, 255)\n\n      stdout.println(`Equal: ${red1 == red2}`)\n      stdout.println(`Not equal: ${red1 <> blue}`)\n\n      // === COMPARISON (<=>) ===\n\n      stdout.println(`Compare: ${red1 <=> blue}`)\n\n      // === STRING ($) ===\n\n      stdout.println(`String: ${red1}`)\n\n      // === ISSET (?) ===\n\n      stdout.println(`IsSet: ${red1?}`)\n\n      // === RECORD with default operator ===\n\n      addr1 <- Address(\"Main St\", \"Springfield\")\n      addr2 <- Address(\"Main St\", \"Springfield\")\n      stdout.println(`Address equal: ${addr1 == addr2}`)\n      stdout.println(`Address: ${addr1}`)","migrationContext":"Java: must manually write equals(), hashCode(), toString(), compareTo(). Lombok @Data generates them. Records (Java 16) auto-generate equals/hashCode/toString. Python: @dataclass generates __eq__, __repr__, __hash__. Rust: #[derive(Eq, Hash, Ord, Debug)] generates trait implementations. Go: no auto-generation, must write comparison functions manually. Kotlin: data class generates equals(), hashCode(), toString(), copy(). EK9: 'default operator' generates ==, <>, <=>, $, #?, ? from all fields in declaration order.","keywords":["automatic","comparison","default","equality","field","generate","hashcode","isset","object-oriented","operator","string"],"primaryTopics":["default operator","generated operator"],"typicalErrors":[{"error":"E08180","correct":"red <- Integer()","incorrect":"red as Integer","explanation":"Class fields must be initialised either with a default value or through constructor assignment. Using 'Integer()' provides a default unset value. Declaring 'red as Integer' without '?' or a value may trigger E08180 if no constructor sets it. See ek9 -h E08180 for details."},{"error":"E50060","correct":"stdout.println(`Equal: ${red1 == red2}`)","incorrect":"stdout.println(red1.equals(red2))","explanation":"EK9 does not have an 'equals()' method. Use the == operator directly for equality comparison. See ek9 -h E50060 for details."},{"error":"E50060","correct":"stdout.println(`Compare: ${red1 <=> blue}`)","incorrect":"stdout.println(red1.compareTo(blue))","explanation":"EK9 does not have a compareTo() method. Use the <=> operator directly for comparison. The 'default operator' auto-generates <=> for you. See ek9 -h E50060 for details."},{"error":"E50060","correct":"stdout.println(`Address: ${addr1}`)","incorrect":"stdout.println(addr1.toString())","explanation":"EK9 does not have a toString() method. Use the $ operator or string interpolation. The 'default operator' auto-generates $ for string conversion. See ek9 -h E50060 for details."},{"error":"E07220","correct":"default operator ==","incorrect":"default operator contains","explanation":"Not all operators support 'default' auto-generation. Operators like 'contains' have no meaningful field-by-field generation semantics. Only ==, <>, <=>, $, #?, and ? can be auto-generated. See ek9 -h E07220 for details."},{"error":"E07030","correct":"override operator ? as pure\n        <- rtn as Boolean: true","incorrect":"default operator","explanation":"Traits cannot have 'default operator' because traits define contracts, not data-based implementations. Auto-generation needs concrete fields which traits do not have. Provide explicit operator implementations in traits instead. See ek9 -h E07030 for details."},{"error":"E07235","correct":"default operator ?","incorrect":"//operator ? omitted","explanation":"Any aggregate with fields must define operator ? for EK9's tri-state semantics. Without it, guard expressions and safe access patterns cannot inspect field state. Use 'default operator ?' or implement manually. See ek9 -h E07235 for details."}],"companions":[]}
{"id":117,"category":"Classes and OOP","question":"How do I create a singleton in EK9?","url":"https://ek9.io/qa/QA0117.html","alternatePhrasings":["Does EK9 support the singleton pattern?","How do I ensure only one instance of a class exists?","What is the EK9 approach to singletons?"],"answer":"EK9 has no traditional singleton pattern. There is no private constructor trick, no static getInstance() method, and no object keyword. The ONLY way to have a single shared instance is through Dependency Injection and Inversion of Control using the component and application registration system.\n\nWHY NO TRADITIONAL SINGLETONS\nTraditional singletons create hidden global state, tight coupling, and testing nightmares. EK9 enforces that shared instances are explicitly registered and injected, making dependencies visible and testable.\n\nHOW SINGLE INSTANCES WORK\nRegister a component in an application definition. All injection points receive the same instance:\n  defines component\n    AppConfig as abstract\n      setting() as abstract\n        <- rtn as String?\n      default operator ?\n  defines application\n    MyApp\n      register ProductionConfig() as AppConfig\nThe '!' suffix on a type declaration triggers injection: config as AppConfig!\n\nLIFECYCLE\nThe registered component instance is created once during application startup. Every injection point across the application receives the same instance.\n\nTHREAD SAFETY\nSince components are shared, use immutable state or MutexLock for mutable data. Pure methods ensure thread-safe reads.\n\nSee Q111 for components and DI. See Q114 for aspects. See Q119 for constructs overview. See Q234 for component lifecycle details.\n\nSee Q330 for why EK9 uses singleton-only DI scope.","ek9Example":"defines module qa.oop.singleton\n\n  defines component\n\n    AppConfig as abstract\n      setting() as abstract\n        <- rtn as String?\n      default operator ?\n\n    ProductionConfig extends AppConfig\n      override setting()\n        <- rtn as String: \"production-mode\"\n      default operator ?\n\n  defines application\n\n    SingletonApp\n      register ProductionConfig() as AppConfig\n\n  defines program\n\n    SingletonDemo() with application of SingletonApp\n      stdout <- Stdout()\n\n      // === SINGLETON via component injection ===\n\n      config1 as AppConfig!\n      config2 as AppConfig!\n\n      // Both reference the same registered instance\n      stdout.println(`Config1: ${config1.setting()}`)\n      stdout.println(`Config2: ${config2.setting()}`)\n\n      // === Same value from both injection points ===\n\n      stdout.println(`Same: ${config1.setting() == config2.setting()}`)","migrationContext":"Java: private constructor + static getInstance(), or Spring @Singleton/@Scope. Python: module-level instances, or __new__ override. Rust: lazy_static! or once_cell for global state. Go: sync.Once for lazy initialisation, package-level variables. Kotlin: 'object' keyword creates a singleton. EK9: component registration provides singleton lifecycle, injection with '!' suffix, no explicit singleton pattern needed, thread safety through pure methods or MutexLock.","keywords":["component","define","inject","instance","lifecycle","object-oriented","pattern","register","safety","shared","singleton","thread"],"primaryTopics":["singleton","single instance"],"typicalErrors":[{"error":"E05120","correct":"override setting()","incorrect":"setting()","explanation":"When implementing an abstract method from the parent component AppConfig, the 'override' keyword is mandatory. ProductionConfig must use 'override setting()'. See ek9 -h E05120 for details."},{"error":"E50040","correct":"AppConfig as abstract","incorrect":"AppConfig","explanation":"Without 'as abstract', AppConfig has an abstract method 'setting()' but the component itself is not marked abstract. The compiler requires types with abstract methods to be declared 'as abstract'. See ek9 -h E50040 for details."},{"error":"E50060","correct":"config1.setting()","incorrect":"config1.getSetting()","explanation":"EK9 does not use Java-style getter naming. The method is 'setting()' not 'getSetting()'. See ek9 -h E50060 for details."},{"error":"E50060","correct":"stdout.println(`Config1: ${config1.setting()}`)","incorrect":"stdout.println(config1.toString())","explanation":"EK9 does not have a toString() method. Use string interpolation or the $ operator instead. See ek9 -h E50060 for details."}],"companions":[],"oracleToolHint":{"tool":"ek9_scaffold","intent":"class","description":"Oracle can generate a singleton pattern class with private constructor and static accessor."}}
{"id":118,"category":"Classes and OOP","question":"How do I implement the builder pattern in EK9?","url":"https://ek9.io/qa/QA0118.html","alternatePhrasings":["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?"],"answer":"EK9 supports builder-like patterns through record merge operations and multiple constructors. The merge operator ':~:' is particularly useful for incremental object construction.\n\nBUILDER IN EK9\nUse a record with the merge operator to build up an object:\n  config <- ServerConfig()\n  config :~: partial1\n  config :~: partial2\nEach merge adds only the SET fields from the source.\n\nFLUENT API PATTERN\nDefine builder methods that return the modified object:\n  withHost()\n    -> host as String\n    <- rtn as ServerConfig: ServerConfig()\n    rtn :=: this\n    rtn.host: host\n\nUSING RECORDS AS BUILDERS\nRecords with public fields and merge operator serve as natural builders. Create partial records and merge them together.\n\nALTERNATIVE NAMED CONSTRUCTOR PARAMETERS\nEK9 supports named parameters in constructors:\n  config <- ServerConfig(host: \"localhost\", port: 8080)\nThis can replace simple builders.\n\nSee Q94 for constructors. See Q97 for records. See Q98 for record operators.","ek9Example":"defines module qa.oop.builder\n\n  defines record\n\n    ServerConfig\n      host <- String()\n      port <- Integer()\n      maxConnections <- Integer()\n\n      ServerConfig()\n        ->\n          host as String\n          port as Integer\n          maxConnections as Integer\n        this.host: host\n        this.port: port\n        this.maxConnections: maxConnections\n\n      operator :=:\n        -> from as ServerConfig\n        host :=: from.host\n        port :=: from.port\n        maxConnections :=: from.maxConnections\n\n      operator :~:\n        -> from as ServerConfig\n        if from.host?\n          host :=: from.host\n        if from.port?\n          port :=: from.port\n        if from.maxConnections?\n          maxConnections :=: from.maxConnections\n\n      operator $ as pure\n        <- rtn as String: `${host}:${port} (max:${maxConnections})`\n\n      default operator ?\n\n  defines program\n\n    BuilderDemo()\n      stdout <- Stdout()\n\n      // === NAMED CONSTRUCTOR PARAMETERS ===\n\n      config1 <- ServerConfig(\"localhost\", 8080, 100)\n      stdout.println(`Direct: ${config1}`)\n\n      // === BUILD WITH MERGE ===\n\n      config2 <- ServerConfig()\n      partial1 <- ServerConfig()\n      partial1.host: \"production.example.com\"\n      config2 :~: partial1\n\n      partial2 <- ServerConfig()\n      partial2.port: 443\n      partial2.maxConnections: 500\n      config2 :~: partial2\n\n      stdout.println(`Merged: ${config2}`)\n\n      // === COPY AND MODIFY ===\n\n      config3 <- ServerConfig()\n      config3 :=: config1\n      config3.port: 9090\n      stdout.println(`Modified copy: ${config3}`)","migrationContext":"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","compile","construct","fluent","incremental","merge","named","object-oriented","parameter","pattern","record","step"],"primaryTopics":[],"typicalErrors":[{"error":"E06180","correct":"defines record","incorrect":"defines class","explanation":"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."},{"error":"E50060","correct":"config2 :~: partial2","incorrect":"config2 :^: partial2","explanation":"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."},{"error":"E50060","correct":"config3 :=: config1","incorrect":"config3 :^: config1","explanation":"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."},{"error":"E50001","correct":"config1 <- ServerConfig(\"localhost\", 8080, 100)","incorrect":"config1 <- ServerConfig.builder().host(\"localhost\").port(8080).build()","explanation":"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."},{"error":"E07430","correct":"operator :~:\n        -> from as ServerConfig","incorrect":"operator :~:\n        -> from as ServerConfig\n        <- rtn as ServerConfig: ServerConfig()","explanation":"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."},{"error":"E50060","correct":"stdout.println(`Direct: ${config1}`)","incorrect":"stdout.println(config1.toString())","explanation":"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."}],"companions":[]}
{"id":119,"category":"Classes and OOP","question":"What EK9 constructs are available and when should I use each?","url":"https://ek9.io/qa/QA0119.html","alternatePhrasings":["What are all the construct types in EK9?","How do I choose between class record trait and component?","What is the complete list of EK9 type constructs?"],"answer":"EK9 provides a rich set of constructs, each designed for specific purposes. Choosing the right construct leads to clearer, more maintainable code.\n\nCONSTRUCT OVERVIEW\nClass: Encapsulated behaviour, private properties, methods, constructors.\nRecord: Transparent data, public properties, ideal for DTOs and values.\nTrait: Behaviour contracts, no mutable state, multiple implementation.\nComponent: DI-managed services, injection with '!', application registration.\nEnumeration: Named constant values, auto-generated operators, iteration.\nText: Internationalized strings, locale-specific, compiler-validated.\nService: REST/HTTP endpoints, operator-to-HTTP mapping, URI binding.\nFunction: Standalone callable units, pure functions, higher-order.\nProgram: Entry points with optional application binding.\nApplication: DI container, component and service registration.\nType: Constrained types derived from built-in types.\nConstant: Module-level immutable values.\n\nDECISION GUIDE\nNeed behaviour with hidden state? Class.\nNeed transparent data? Record.\nNeed shared behaviour contract? Trait.\nNeed injectable service? Component.\nNeed fixed set of values? Enumeration.\nNeed localised text? Text.\nNeed HTTP endpoint? Service.\n\nPHILOSOPHY\nEK9 favours composition over inheritance (closed by default), behaviour through traits, and explicit DI through components. Each construct has a single clear purpose.\n\nSee Q93 for classes. See Q97 for records. See Q99 for enumerations. See Q106 for traits. See Q111 for components. See Q49 for functions. See Q17 for entry points. See Q1 for minimal program.","ek9Example":"defines module qa.oop.overview\n\n  defines type\n\n    Rating\n      Low\n      Medium\n      High\n\n  defines record\n\n    Item\n      name <- String()\n      rating <- Rating()\n\n      Item()\n        ->\n          name as String\n          rating as Rating\n        this.name: name\n        this.rating: rating\n\n      default operator\n\n  defines function\n\n    describeItem() as pure\n      -> item as Item\n      <- rtn as String: `${item.name} [${item.rating}]`\n\n  defines class\n\n    Catalog\n      items <- List() of Item\n\n      add()\n        -> item as Item\n        items += item\n\n      count() as pure\n        <- rtn as Integer: length items\n\n      listAll()\n        <- rtn as List of Item: items\n\n      default operator ?\n\n  defines program\n\n    OverviewDemo()\n      stdout <- Stdout()\n\n      // === ENUMERATION ===\n\n      priority <- Rating.High\n      stdout.println(`Rating: ${priority}`)\n\n      // === RECORD ===\n\n      item <- Item(\"Widget\", Rating.Medium)\n      stdout.println(`Item: ${item}`)\n\n      // === FUNCTION ===\n\n      stdout.println(describeItem(item))\n\n      // === CLASS ===\n\n      catalog <- Catalog()\n      catalog.add(Item(\"Gadget\", Rating.High))\n      catalog.add(Item(\"Tool\", Rating.Low))\n      catalog.add(item)\n\n      stdout.println(`Catalog size: ${catalog.count()}`)\n\n      for entry in catalog.listAll()\n        stdout.println(`  ${describeItem(entry)}`)","migrationContext":"Java: class, interface, enum, record (Java 16), annotation, no built-in component/service/text constructs. Python: class, no enum until 3.4, no built-in DI or service constructs. Rust: struct, enum, trait, impl, no class or component concepts. Go: struct, interface, no class, enum, component, or service constructs. Kotlin: class, data class, interface, enum class, object, no built-in DI or service. EK9: twelve distinct constructs each with clear purpose - class, record, trait, component, enumeration, text, service, function, program, application, type, constant.","keywords":["application","class","component","constant","construct","enumeration","function","object-oriented","oop","overview","program","record","service","text","trait"],"primaryTopics":["constructs overview","EK9 constructs"],"typicalErrors":[{"error":"E06180","correct":"stdout.println(`Catalog size: ${catalog.count()}`)","incorrect":"stdout.println(`Catalog size: ${catalog.items}`)","explanation":"Record fields like 'item.name' are public, but class fields like 'catalog.items' are private. Accessing a private class field from outside triggers E06180. Use the class's public methods instead. See ek9 -h E06180 for details."},{"error":"E50060","correct":"describeItem(item)","incorrect":"item.describe()","explanation":"Item is a record — it has no 'describe' method. In EK9, behaviour belongs in standalone functions, not on records. Calling a non-existent method triggers E50060. Use describeItem(item) instead. See ek9 -h E50060 for details."},{"error":"E07290","correct":"default operator","incorrect":"describe()\n        <- rtn as String: name","explanation":"Records can only have constructors and operators — not named methods. Defining 'describe()' on Item triggers E07290. Use a standalone function like describeItem() instead. See ek9 -h E07290 for details."},{"error":"E50060","correct":"stdout.println(`Item: ${item}`)","incorrect":"stdout.println(item.toString())","explanation":"EK9 does not have toString(). Use the $ operator or string interpolation — the record's 'default operator' auto-generates $ for string conversion. See ek9 -h E50060 for details."},{"error":"E50001","correct":"priority <- Rating.High","incorrect":"priority <- Rating.HIGH","explanation":"EK9 enumeration values are case-sensitive. 'HIGH' does not match the defined value 'High'. Check the exact casing of enumeration values. See ek9 -h E50001 for details."}],"companions":[]}
{"id":120,"category":"Collections and Data Structures","question":"How do I sort a collection in EK9?","url":"https://ek9.io/qa/QA0120.html","alternatePhrasings":["How do I order a list in EK9?","How does the sort pipeline operation work in EK9?","How do I sort by a custom comparator in EK9?"],"answer":"EK9 sorts collections through stream pipelines using the | sort operation. Natural sorting uses the <=> operator defined on the element type. Custom sorting uses a comparator function.\n\nSORT IN STREAMS\nSorting is a pipeline stage, not a method call on the collection:\n  sorted <- cat items | sort | collect as List of String\nThis creates a new sorted list. The original list is unchanged.\n\nNATURAL SORT\n| sort uses the <=> (comparison) operator defined on the element type:\n  cat names | sort > stdout\nFor built-in types (String, Integer, Float, Date), <=> is already defined. For custom types, you must implement operator <=>.\n\nSORT BY COMPARATOR\n| sort by function uses a custom comparator:\n  cat books | sort by comparingTitle | collect as List of Book\nThe comparator is a pure function taking two arguments and returning Integer (-1, 0, or 1).\n\nIMPLEMENTING COMPARISON FOR CUSTOM TYPES\nDefine operator <=> on your record or class to enable natural sort:\n  operator <=> as pure\n    -> other as Product\n    <- rtn as Integer: name <=> other.name\n\nCOMPARATOR AS DYNAMIC FUNCTION\nCreate comparators inline for one-off sorting:\n  comparingPrice <- () extends Comparator of Product as pure function\n    r:=? t1.price() <=> t2.price()\nThis creates a function that compares by price field.\n\nSee Q45 for List basics. See Q89 for basic stream pipelines. See Q96 for operator overloading. See Q121 for PriorityQueue (maintained order). See Q125 for head/tail/skip to limit sorted results.","ek9Example":"defines module qa.collections.sort\n\n  defines class\n\n    Product\n      name as String?\n      price as Float?\n\n      default private Product() as pure\n\n      Product() as pure\n        ->\n          name as String\n          price as Float\n        this.name :=? name\n        this.price :=? price\n\n      name() as pure\n        <- rtn as String: String(name)\n\n      price() as pure\n        <- rtn as Float: Float(price)\n\n      operator <=> as pure\n        -> other as Product\n        <- rtn as Integer: name <=> other.name\n\n      operator == as pure\n        -> other as Product\n        <- rtn as Boolean: name == other.name and price == other.price\n\n      operator $ as pure\n        <- rtn as String: `${name} \\$${price}`\n\n      operator #? as pure\n        <- rtn as Integer: #?name\n\n      override operator ? as pure\n        <- rtn as Boolean: name? and price?\n\n  defines function\n\n    comparingPrice() as pure\n      ->\n        t1 as Product\n        t2 as Product\n      <-\n        rtn as Integer: t1.price() <=> t2.price()\n\n  defines program\n\n    SortCollectionDemo()\n      stdout <- Stdout()\n\n      products <- [\n        Product(\"Banana\", 1.20),\n        Product(\"Apple\", 0.90),\n        Product(\"Cherry\", 3.50),\n        Product(\"Date\", 5.00)\n        ]\n\n      // === NATURAL SORT ===\n\n      // Sort by name (uses operator <=> defined on Product)\n      byName <- cat products | sort | collect as List of Product\n      stdout.println(`By name: ${byName}`)\n\n      // === SORT BY COMPARATOR ===\n\n      // Sort by price using named comparator\n      byPrice <- cat products | sort by comparingPrice | collect as List of Product\n      stdout.println(`By price: ${byPrice}`)\n\n      // === SORT WITH DYNAMIC COMPARATOR ===\n\n      // Inline comparator for one-off sort\n      reverseByName <- () extends Comparator of Product as pure function\n        r:=? t2.name() <=> t1.name()\n\n      descending <- cat products | sort by reverseByName | collect as List of Product\n      stdout.println(`Descending: ${descending}`)","migrationContext":"Java: Collections.sort() or list.stream().sorted(Comparator.comparing()). Python: sorted(list, key=lambda x: x.field). JavaScript: array.sort((a, b) => a.field - b.field). Rust: vec.sort() or vec.sort_by_key(). Go: sort.Slice() with less function. EK9: cat list | sort or cat list | sort by comparator in a pipeline, comparator defined as pure function or dynamic function extending Comparator of T.","keywords":["ascending","collection","comparator","comparison","custom","data-structure","descending","list","natural","order","pipeline","sort","stream"],"primaryTopics":["sort","sort collection","sort list"],"typicalErrors":[{"error":"E50060","correct":"byName <- cat products | sort | collect as List of Product","incorrect":"byName <- products.sort()","explanation":"EK9 sorts through stream pipelines using '| sort', not method calls like .sort(). Sorting is a pipeline stage, not a method on the collection. See ek9 -h E50060 for details."},{"error":"E07235","correct":"      override operator ? as pure\n        <- rtn as Boolean: name? and price?","incorrect":"      //no isSet operator","explanation":"A class with fields must define operator ? for tri-state semantics. Without it, guard expressions and safe access patterns cannot inspect field state. See ek9 -h E07235 for details."},{"error":"E50060","correct":"stdout.println(`By name: ${byName}`)","incorrect":"stdout.println(byName.toString())","explanation":"EK9 does not have toString(). Use the $ operator or string interpolation. See ek9 -h E50060 for details."},{"error":"E50001","correct":"cat products | sort by comparingPrice | collect as List of Product","incorrect":"cat products | sort by Collections.comparing(Product.price) | collect as List of Product","explanation":"EK9 does not have Java-style Collections utility classes or method references. 'Collections' is not a known type. Use a named comparator function that takes two parameters and returns Integer. See ek9 -h E50001 for details."}],"companions":[]}
{"id":121,"category":"Collections and Data Structures","question":"How does PriorityQueue work in EK9?","url":"https://ek9.io/qa/QA0121.html","alternatePhrasings":["How do I use a priority queue in EK9?","How do I create a bounded sorted collection in EK9?","How do I get the top N items from a collection in EK9?"],"answer":"EK9 provides a generic PriorityQueue type that maintains elements in sorted order. It supports bounded size for top-N patterns and integrates with stream pipelines.\n\nCREATION\nCreate a PriorityQueue with an initial element:\n  pq <- PriorityQueue(\"first\")\nOr create an empty typed queue:\n  pq <- PriorityQueue() of String\n\nCOMPARATOR SETUP\nA PriorityQueue needs a comparator to determine order:\n  comparator <- () extends Comparator of String as pure function (r:=? t1 <=> t2)\n  pq <- PriorityQueue(\"Bill\").withComparator(comparator)\nThe comparator is a pure function using the <=> operator.\n\nADDING ELEMENTS\nUse += to add elements:\n  pq += \"Ted\"\n  pq += \"Excellent\"\nElements are automatically maintained in priority order.\n\nBOUNDED QUEUES\nCreate a bounded queue that keeps only the top N items:\n  topThree <- pq.withSize(3)\nWhen more than N elements are added, the lowest-priority element is dropped. This is the efficient top-N pattern.\n\nACCESSING ORDERED RESULTS\nGet elements as a sorted list:\n  items <- pq.list()\nOr reverse the order:\n  reversed <- pq.list().reverse()\n\nSTREAM INTEGRATION\nUse PriorityQueue as a pipeline terminal:\n  cat items | filter by isGood | collect as PriorityQueue of String\nOr as a pipeline source:\n  cat pq | map with transform > stdout\n\nSee Q45 for List basics. See Q120 for sorting. See Q122 for collect as custom aggregation. See Q126 for choosing the right collection type.","ek9Example":"defines module qa.collections.priorityqueue\n\n  defines function\n\n    comparingStrings() as pure\n      ->\n        t1 as String\n        t2 as String\n      <-\n        rtn as Integer: t1 <=> t2\n\n    suitableLength() as pure\n      -> item as String\n      <- rtn <- Boolean()\n      minLength <- 3\n      rtn: length item > minLength\n\n  defines program\n\n    PriorityQueueDemo()\n      stdout <- Stdout()\n\n      // === CREATION WITH COMPARATOR ===\n\n      comparator <- () extends Comparator of String as pure function (r:=? t1 <=> t2)\n      pq <- PriorityQueue(\"Bill\").withComparator(comparator)\n\n      // === ADDING ELEMENTS ===\n\n      pq += \"And\"\n      pq += \"Ted\"\n      pq += \"Excellent\"\n      pq += \"Adventure\"\n\n      allEntries <- pq.list()\n      stdout.println(`All entries: ${allEntries}`)\n\n      // === BOUNDED QUEUE (TOP-N) ===\n\n      topThree <- pq.withSize(3)\n      limitedEntries <- topThree.list()\n      stdout.println(`Top 3: ${limitedEntries}`)\n\n      // === STREAM AS SOURCE ===\n\n      cat allEntries | filter by suitableLength > stdout\n\n      // === EMPTY QUEUE ===\n\n      emptyQ <- PriorityQueue() of String\n      require emptyQ?\n      require emptyQ is empty\n      stdout.println(`Empty PQ isSet: ${emptyQ?}`)","migrationContext":"Java: PriorityQueue<T> with Comparator, no bounded size built-in (must manage manually). Python: heapq module, heapq.nlargest() for top-N. JavaScript: no built-in priority queue, use libraries. Rust: BinaryHeap<T> with Reverse wrapper. Go: container/heap interface. EK9: PriorityQueue(elem).withComparator(comp).withSize(n) fluent API, integrated with stream pipelines.","keywords":["bounded","collect","collection","comparator","data-structure","order","priority","queue","sorted","top","withComparator","withSize"],"primaryTopics":["priority queue","heap"],"typicalErrors":[{"error":"E50060","correct":"allEntries <- pq.list()","incorrect":"allEntries <- pq.peek()","explanation":"PriorityQueue does not have a Java-style peek() method. Use .list() to get all entries as a sorted List. See ek9 -h E50060 for details."},{"error":"E50060","correct":"allEntries <- pq.list()","incorrect":"allEntries <- pq.toArray()","explanation":"PriorityQueue does not have a Java-style toArray() method. Use .list() to get entries as a List. See ek9 -h E50060 for details."},{"error":"E50060","correct":"pq += \"Ted\"","incorrect":"pq.add(\"Ted\")","explanation":"PriorityQueue does not have a Java-style .add() method. Use the += operator to add elements. See ek9 -h E50060 for details."},{"error":"E50060","correct":"stdout.println(`All entries: ${allEntries}`)","incorrect":"stdout.println(allEntries.toString())","explanation":"EK9 does not have toString(). Use the $ operator or string interpolation. See ek9 -h E50060 for details."}],"companions":[]}
{"id":122,"category":"Collections and Data Structures","question":"How do I use collect as to aggregate stream results in EK9?","url":"https://ek9.io/qa/QA0122.html","alternatePhrasings":["How do I create a custom stream collector in EK9?","How does the operator | work for stream aggregation in EK9?","How do I reduce a stream to a single value in EK9?","What is the EK9 equivalent of fold or reduce?","How do I accumulate values from a stream in EK9?"],"answer":"EK9 stream pipelines use | collect as Type to gather results. Built-in collectors handle List, Integer, and String. For custom aggregation, define operator | on a record.\n\nBUILT-IN COLLECTORS\nCollect into a list:\n  evens <- cat numbers | filter by isEven | collect as List of Integer\nCollect by summing integers:\n  total <- cat numbers | collect as Integer\nCollect by concatenating strings:\n  joined <- cat words | collect as String\n\nCUSTOM AGGREGATOR PATTERN\nDefine a record with operator | to receive stream items one at a time:\n  Stats\n    count as Integer: Integer()\n    total as Integer: Integer()\n    operator |\n      -> arg as Integer\n      if arg?\n        if ~total?\n          count := 1\n          total :=: arg\n        else\n          count++\n          total += arg\nThe stream feeds each item to operator | in sequence.\n\nTRI-STATE FIRST-ITEM DETECTION\nUse ~total? to detect the first item (total is unset). On first item, initialize with :=: (copy operator). On subsequent items, accumulate with +=.\n\nUSAGE\n  stats <- for i in 1 ... 12 | collect as Stats\nThe for-range generates integers 1 through 12, piped into the Stats aggregator.\n\nSee Q80 for for-range expression as alternative accumulation. See Q82 for for-in expression as alternative fold/reduce. See Q89 for basic collect patterns. See Q96 for operator overloading. See Q120 for sort before collect. See Q124 for group by aggregation. See Q235 for complete stream operations reference. See Q237 for streams vs loops decision guide. See Q286 for batch accumulation with stream collect.","ek9Example":"defines module qa.collections.collectas\n\n  defines record\n\n    Stats\n      count as Integer: Integer()\n      total as Integer: Integer()\n      average as Float: Float()\n\n      operator $ as pure\n        <- rtn as String: String()\n        if total?\n          rtn :=? `[${count}, ${average}, ${total}]`\n\n      operator |\n        -> arg as Integer\n\n        if arg?\n          if ~total?\n            count := 1\n            total :=: arg\n          else\n            count++\n            total += arg\n          average := (#^total) / count\n\n      default operator ?\n\n  defines function\n\n    isEven() as pure\n      -> num as Integer\n      <- rtn as Boolean: num mod 2 == 0\n\n    doubleIt() as pure\n      -> num as Integer\n      <- rtn as Integer: num * 2\n\n  defines program\n\n    CollectAsDemo()\n      stdout <- Stdout()\n\n      numbers <- [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]\n\n      // === BUILT-IN: COLLECT AS LIST ===\n\n      evens <- cat numbers | filter by isEven | collect as List of Integer\n      stdout.println(`Evens: ${evens}`)\n\n      // === BUILT-IN: COLLECT AS INTEGER (SUM) ===\n\n      total <- cat numbers | collect as Integer\n      stdout.println(`Sum: ${total}`)\n\n      // === CUSTOM AGGREGATOR ===\n\n      stats <- for i in 1 ... 12 | collect as Stats\n      stdout.println(`Stats: ${stats}`)\n\n      // === CUSTOM AGGREGATOR WITH FILTER ===\n\n      evenStats <- cat numbers | filter by isEven | collect as Stats\n      stdout.println(`Even stats: ${evenStats}`)","migrationContext":"Java: Collectors.reducing(), Collectors.summarizingInt(), custom Collector interface. Python: functools.reduce() or manual accumulation. JavaScript: array.reduce((acc, item) => ..., initial). Rust: iter.fold(initial, |acc, item| ...). Go: manual loop accumulation. EK9: define operator | on a record, use | collect as MyRecord in pipeline, tri-state ~var? for first-item detection.","keywords":["accumulate","accumulator","aggregate","collect","collection","custom","data-structure","fold","operator","pipe","reduce","reducer","result","statistics","stream","sum"],"primaryTopics":[],"typicalErrors":[{"error":"E50060","correct":"evens <- cat numbers | filter by isEven | collect as List of Integer","incorrect":"evens <- numbers.stream().filter(isEven).collect()","explanation":"EK9 does not have .stream() or .collect() methods. Use the cat source | filter by pred | collect as Type pipeline syntax. See ek9 -h E50060 for details."},{"error":"E07235","correct":"default operator ?","incorrect":"//no isSet operator","explanation":"A record with fields must define operator ? for tri-state semantics. A Stats record used with '| collect as' needs isSet so the pipeline can check if the result is valid. See ek9 -h E07235 for details."},{"error":"E50060","correct":"stdout.println(`Evens: ${evens}`)","incorrect":"stdout.println(evens.toString())","explanation":"EK9 does not have toString(). Use string interpolation or the $ operator. See ek9 -h E50060 for details."},{"error":"E50060","correct":"stdout.println(`Sum: ${total}`)","incorrect":"stdout.println(total.toString())","explanation":"EK9 does not have toString(). Use string interpolation or the $ operator for string conversion. See ek9 -h E50060 for details."},{"error":"E07520","correct":"cat numbers | filter by isEven | collect as List of Integer","incorrect":"cat numbers | filter by doubleIt | collect as List of Integer","explanation":"A function used with '| filter by' must return Boolean. The function doubleIt() returns Integer, not Boolean, so it cannot be used as a predicate. Use a function that returns Boolean for filtering. See ek9 -h E07520 for details."}],"companions":[]}
{"id":123,"category":"Collections and Data Structures","question":"How do I flatten nested collections and optionals in EK9?","url":"https://ek9.io/qa/QA0123.html","alternatePhrasings":["How does the flatten pipeline operation work in EK9?","How do I merge nested lists into a single list in EK9?","How do I flatten a list of lists in EK9?","What is the EK9 equivalent of flatMap?"],"answer":"EK9 uses | flatten in stream pipelines to collapse nested structures into flat sequences. This is commonly used after group operations that produce lists of lists.\n\nFLATTEN LIST OF LIST\nAfter a group operation, you have List of List of T. Flatten merges into List of T:\n  cat grouped | flatten | collect as List of String\nEach inner list's elements are emitted in order.\n\nFLATTEN IN PIPELINES\nFlatten typically appears after group and map stages:\n  cat items\n    | sort by key\n    | group by category\n    | map by transform\n    | flatten\n    | collect as List of Item\nThis is the standard group-process-flatten pattern.\n\nWHEN FLATTEN IS NEEDED\nAny pipeline operation that wraps results in an extra layer needs flatten to unwrap:\n  group by produces List of List of T from List of T\n  map after group produces List of List of U from List of List of T\nFlatten removes exactly one level of nesting.\n\nFLATTEN WITH OUTPUT\nPipe flattened results directly to stdout:\n  cat groupedItems | flatten > stdout\nEach element prints on its own line.\n\nSee Q85 for Optional stream patterns. See Q89 for basic stream pipelines. See Q124 for group by that creates nested lists.","ek9Example":"defines module qa.collections.flatten\n\n  defines function\n\n    firstChar() as pure\n      -> word as String\n      <- rtn as String: $#<word\n\n  defines program\n\n    FlattenDemo()\n      stdout <- Stdout()\n\n      words <- [\"apple\", \"avocado\", \"banana\", \"blueberry\", \"cherry\", \"cranberry\"]\n\n      // === GROUP THEN FLATTEN ===\n\n      // Group by first character, then flatten back\n      grouped <- cat words | sort | group by firstChar | collect as List of List of String\n      stdout.println(`Grouped: ${grouped}`)\n\n      flat <- cat grouped | flatten | collect as List of String\n      stdout.println(`Flattened: ${flat}`)\n\n      // === FLATTEN WITH DIRECT OUTPUT ===\n\n      cat grouped | flatten > stdout\n\n      // === NESTED LIST CREATION AND FLATTEN ===\n\n      nested <- [\n        [\"one\", \"two\"],\n        [\"three\", \"four\"],\n        [\"five\", \"six\"]\n        ]\n\n      allItems <- cat nested | flatten | collect as List of String\n      stdout.println(`All items: ${allItems}`)","migrationContext":"Java: stream.flatMap(Collection::stream) or Stream.concat(). Python: itertools.chain.from_iterable() or [item for sublist in nested for item in sublist]. JavaScript: array.flat() or array.flatMap(). Rust: iter.flatten() or iter.flat_map(). Go: manual nested loops. EK9: | flatten in pipeline after group or nested operations, removes one level of nesting.","keywords":["collapse","collection","concatMap","data-structure","flat","flatMap","flatten","group","list","merge","nested","optional","pipeline","stream","unwrap"],"primaryTopics":[],"typicalErrors":[{"error":"E50060","correct":"flat <- cat grouped | flatten | collect as List of String","incorrect":"flat <- grouped.flatten()","explanation":"EK9 has no flatten() method on collections. Use | flatten in a stream pipeline to collapse nested structures. The pipeline form is: cat nested | flatten | collect as List of T. See ek9 -h E50060 for details."},{"error":"E50001","correct":"cat nested | flatten | collect as List of String","incorrect":"cat nested | flatten myFunction | collect as List of String","explanation":"Flatten takes no function argument — it simply unwraps the nested structure. Unlike Java's flatMap() which combines map and flatten, EK9 separates these operations. See ek9 -h E50001 for details."},{"error":"E07830","correct":"allItems <- cat nested | flatten | collect as List of String","incorrect":"allItems <- cat nested | flatten | collect as List of Date","explanation":"After flattening List of List of String, the stream type is String. Collecting into List of Date fails because String and Date are incompatible types. The collect target must match the stream type. See ek9 -h E07830 for details."},{"error":"E50060","correct":"stdout.println(`Flattened: ${flat}`)","incorrect":"stdout.println(flat.toString())","explanation":"EK9 does not have toString(). Use string interpolation or the $ operator. See ek9 -h E50060 for details."},{"error":"E50060","correct":"stdout.println(`All items: ${allItems}`)","incorrect":"stdout.println(allItems.stream().collect())","explanation":"EK9 does not have Java-style .stream().collect() method chains. Use pipe syntax: 'cat items | collect as Type'. See ek9 -h E50060 for details."}],"companions":[]}
{"id":124,"category":"Collections and Data Structures","question":"How do I group and aggregate collection data in EK9?","url":"https://ek9.io/qa/QA0124.html","alternatePhrasings":["How does group by work in EK9 stream pipelines?","How do I group items by a key and then process each group in EK9?","How do I use filter with to keep groups in EK9?"],"answer":"EK9 stream pipelines support grouping items by a key extractor function. After grouping, you can filter, map, and flatten the groups.\n\nGROUP BY KEY EXTRACTOR\nGroup by uses a function that extracts a key from each element:\n  cat items | group by extractCategory | collect as List of List of Item\nElements with the same key value are collected into the same inner list. The input must be sorted by the same key first.\n\nFILTER WITH (GROUPS)\nKeep groups matching a predicate on each group (List of T):\n  | filter with hasEnoughItems\nThe predicate receives the entire group (a List), not individual elements.\n\nGROUP THEN MAP\nTransform each group using a mapping function:\n  | map by processGroup\nThe function receives a List of T and returns a transformed result.\n\nFULL PIPELINE PATTERN\nThe complete group-process-flatten pattern:\n  cat items\n    | sort by keyExtractor\n    | group by keyExtractor\n    | filter with filterPredicate\n    | map by groupTransformer\n    | flatten\n    > stdout\nSort first to ensure correct grouping. Group, filter, transform, then flatten back to a flat stream.\n\nSee Q89 for basic stream pipelines. See Q120 for sorting before group. See Q122 for collect as aggregation. See Q123 for flatten after group. See Q125 for head/tail/skip to limit results.","ek9Example":"defines module qa.collections.groupaggregate\n\n  defines class\n\n    Sale\n      region as String?\n      amount as Float?\n\n      default private Sale() as pure\n\n      Sale() as pure\n        ->\n          region as String\n          amount as Float\n        this.region :=? region\n        this.amount :=? amount\n\n      region() as pure\n        <- rtn as String: String(region)\n\n      amount() as pure\n        <- rtn as Float: Float(amount)\n\n      operator $ as pure\n        <- rtn as String: `${region}:${amount}`\n\n      operator <=> as pure\n        -> other as Sale\n        <- rtn as Integer: region <=> other.region\n\n      operator #? as pure\n        <- rtn as Integer: #?region\n\n      override operator ? as pure\n        <- rtn as Boolean: region? and amount?\n\n  defines function\n\n    regionKey() as pure\n      -> sale as Sale\n      <- rtn as String: sale.region()\n\n    comparingRegion() as pure\n      ->\n        t1 as Sale\n        t2 as Sale\n      <-\n        rtn as Integer: t1.region() <=> t2.region()\n\n    hasTwoOrMore() as pure\n      -> group as List of Sale\n      <- rtn <- Boolean()\n      expectedGroups <- 2\n      rtn: length group >= expectedGroups\n\n  defines program\n\n    GroupAggregateDemo()\n      stdout <- Stdout()\n\n      sales <- [\n        Sale(\"North\", 100.0),\n        Sale(\"South\", 200.0),\n        Sale(\"North\", 150.0),\n        Sale(\"East\", 300.0),\n        Sale(\"South\", 175.0),\n        Sale(\"North\", 125.0)\n        ]\n\n      // === GROUP BY REGION ===\n\n      grouped <- cat sales\n        | sort by comparingRegion\n        | group by regionKey\n        | collect as List of List of Sale\n      stdout.println(`Grouped: ${grouped}`)\n\n      // === FILTER GROUPS WITH 2+ SALES ===\n\n      filtered <- cat sales\n        | sort by comparingRegion\n        | group by regionKey\n        | filter with hasTwoOrMore\n        | collect as List of List of Sale\n      stdout.println(`Filtered: ${filtered}`)\n\n      // === FULL PIPELINE: GROUP, FILTER, FLATTEN ===\n\n      cat sales\n        | sort by comparingRegion\n        | group by regionKey\n        | filter with hasTwoOrMore\n        | flatten\n        > stdout","migrationContext":"Java: Collectors.groupingBy() with downstream collectors. Python: itertools.groupby() (requires pre-sort). JavaScript: manual reduce into Map or lodash groupBy. Rust: itertools group_by() (requires pre-sort). Go: manual loop with map. EK9: cat | sort by key | group by key | filter with | map by | flatten pipeline, requires pre-sort like Python/Rust itertools.","keywords":["aggregate","category","collection","data-structure","extractor","filter","flatten","group","groupBy","groupingBy","key","list","map","partition","pipeline","sort"],"primaryTopics":[],"typicalErrors":[{"error":"E50060","correct":"sale.region()","incorrect":"sale.getRegion()","explanation":"EK9 uses short method names without 'get' prefix. Sale has region() and amount() not getRegion() and getAmount(). Triggers E50060 — method not resolved. See ek9 -h E50060 for details."},{"error":"E50060","correct":"stdout.println(`Grouped: ${grouped}`)","incorrect":"stdout.println(grouped.toString())","explanation":"List has no toString() method. Use the $ prefix operator or string interpolation for string conversion. See ek9 -h E50060 for details."},{"error":"E07830","correct":"| collect as List of List of Sale","incorrect":"| collect as List of Sale","explanation":"Group produces List of T from a stream of T — each group is a list. So the collection must be List of List of T, not List of T. A List of String cannot receive List of String items via the pipe operator. See ek9 -h E07830 for details."},{"error":"E50030","correct":"| group by regionKey","incorrect":"| group by hasTwoOrMore","explanation":"The group function's parameter type must match the stream's element type. Using a function that accepts Date on a String stream triggers E50030 because the types are incompatible. See ek9 -h E50030 for details."},{"error":"E06300","correct":"| group by regionKey\n        | filter with hasTwoOrMore","incorrect":"| group by comparingRegion\n        | filter with hasTwoOrMore","explanation":"The group by function must accept exactly one argument — the current stream element. A function with two parameters cannot be used for grouping. See ek9 -h E06300 for details."},{"error":"E07470","correct":"| sort by comparingRegion","incorrect":"| sort by regionKey","explanation":"The sort by comparator must accept exactly two parameters of the element type and return Integer. A single-parameter function cannot compare two elements. Sort is required before group to ensure correct grouping. See ek9 -h E07470 for details."},{"error":"E50060","correct":"stdout.println(`Filtered: ${filtered}`)","incorrect":"stdout.println(filtered.toString())","explanation":"List has no toString() method. Use the $ prefix operator or string interpolation for string conversion. See ek9 -h E50060 for details."}],"companions":[]}
{"id":125,"category":"Collections and Data Structures","question":"How do I use head, tail, and skip to limit streams in EK9?","url":"https://ek9.io/qa/QA0125.html","alternatePhrasings":["How do I take the first N items from a stream in EK9?","How do I skip elements in a stream pipeline in EK9?","What is the EK9 equivalent of break in a loop for limiting results?","How do I stop processing early in EK9 without break?"],"answer":"EK9 provides head, tail, and skip pipeline operations to control how many elements flow through a stream. These replace the need for break or early exit patterns.\n\nHEAD N\nTake only the first N elements from the stream:\n  cat items | head 3 | collect as List of String\nElements after the first 3 are discarded. This is EK9's replacement for break in loops.\n\nTAIL N\nTake only the last N elements from the stream:\n  cat items | tail 2 | collect as List of String\nThe pipeline buffers elements and only emits the final N.\n\nSKIP N\nSkip the first N elements, pass the rest:\n  cat items | skip 5 | collect as List of String\nElements 1 through 5 are discarded, the rest flow through.\n\nREPLACING BREAK WITH HEAD\nInstead of a loop with break after finding enough items, use head:\n  top5 <- cat allItems | sort | head 5 | collect as List of Item\nThis is cleaner and eliminates the bug-prone break pattern.\n\nTHE ONLY EARLY EXIT MECHANISM\nEK9 has no break, return, or continue statements. Stream pipelines with head are the ONLY mechanism for efficient early exit processing in EK9. Instead of looping through a collection with a break condition, use:\n  cat source | filter by predicate | head N | collect as List of T\nThis is not just a convenience pattern but the designed replacement for break. See Q64 for for-range loops, Q65 for for-in loops, Q66 for while loops, and Q67 for do-while loops, none of which have break.\n\nCOMBINING LIMITERS\nChain skip, head, and tail for windowing:\n  cat items | skip 2 | head 3 | collect as List of String\nSkip 2, then take 3 means elements 3, 4, 5 from the original.\n\nSee Q89 for basic stream pipelines. See Q120 for sort with head (top-N). See Q124 for group by with limiting. See Q145 for replacing break and continue with streams. See Q235 for complete stream operations reference. See Q237 for streams vs loops decision guide. See Q284 for find-first-match patterns. See Q287 for bounded processing with head.","ek9Example":"defines module qa.collections.headtailskip\n\n  defines program\n\n    HeadTailSkipDemo()\n      stdout <- Stdout()\n\n      items <- [\"alpha\", \"bravo\", \"charlie\", \"delta\", \"echo\", \"foxtrot\", \"golf\"]\n      stdout.println(`Original: ${items}`)\n\n      // === HEAD N ===\n\n      firstThree <- cat items | head 3 | collect as List of String\n      stdout.println(`Head 3: ${firstThree}`)\n\n      // === TAIL N ===\n\n      lastTwo <- cat items | tail 2 | collect as List of String\n      stdout.println(`Tail 2: ${lastTwo}`)\n\n      // === SKIP N ===\n\n      afterTwo <- cat items | skip 2 | collect as List of String\n      stdout.println(`Skip 2: ${afterTwo}`)\n\n      // === COMBINING: SKIP THEN HEAD ===\n\n      // Elements 3, 4, 5 (skip 2, take 3)\n      window <- cat items | skip 2 | head 3 | collect as List of String\n      stdout.println(`Skip 2 head 3: ${window}`)\n\n      // === SORT THEN HEAD (TOP-N) ===\n\n      sorted <- cat items | sort | head 3 | collect as List of String\n      stdout.println(`Top 3 sorted: ${sorted}`)\n\n      // === DIRECT OUTPUT ===\n\n      cat items | skip 4 > stdout","migrationContext":"Java: stream.limit(n), stream.skip(n), no built-in tail. Python: itertools.islice(iter, n), list[-n:] for tail. JavaScript: array.slice(0, n) for head, array.slice(-n) for tail, array.slice(n) for skip. Rust: iter.take(n), iter.skip(n). Go: manual index slicing. EK9: | head n, | tail n, | skip n as pipeline operations, chain for windowing.","keywords":["alternative","break","collection","data-structure","early","exit","first","head","last","limit","loop","pipeline","replace","skip","stop","stream","tail","take","window"],"primaryTopics":[],"typicalErrors":[{"error":"E50060","correct":"lastTwo <- cat items | tail 2 | collect as List of String","incorrect":"lastTwo <- items.subList(items.size() - 2, items.size())","explanation":"EK9 has no subList() method. Use | tail N in a stream pipeline to take the last N elements. See ek9 -h E50060 for details."},{"error":"E07560","correct":"cat items | head 3 | collect as List of String","incorrect":"cat items | head 0 | collect as List of String","explanation":"The head, tail, and skip operations require a positive integer (greater than zero). Zero and negative values are rejected at compile time. Use a value of 1 or greater. See ek9 -h E07560 for details."},{"error":"E07830","correct":"| head 3 | collect as List of String","incorrect":"| head 3 | collect as List of Integer","explanation":"A supplier function used with head, tail, or skip must return Integer. A function returning Date or any non-Integer type triggers E07550. The count value must be an Integer. See ek9 -h E07550 for details."},{"error":"E07560","correct":"cat items | tail 2 | collect as List of String","incorrect":"cat items | tail 0 | collect as List of String","explanation":"The head, tail, and skip operations require a positive integer (greater than zero). Zero is rejected at compile time. Use a value of 1 or greater. See ek9 -h E07560 for details."},{"error":"E07560","correct":"cat items | skip 2 | head 3 | collect as List of String","incorrect":"cat items | skip 0 | head 3 | collect as List of String","explanation":"The skip operation requires a positive integer (greater than zero). Zero is rejected at compile time. Use a value of 1 or greater. See ek9 -h E07560 for details."},{"error":"E07830","correct":"cat items | sort | head 3 | collect as List of String","incorrect":"cat items | sort | head 3 | collect as List of Integer","explanation":"The collect type must match the pipeline's element type. A String pipeline cannot collect into List of Integer because there is no pipe operator between String and Integer. See ek9 -h E07830 for details."},{"error":"E07560","correct":"cat items | skip 4 > stdout","incorrect":"cat items | skip -1 > stdout","explanation":"Skip requires a positive integer greater than zero. Negative values like -1 are rejected at compile time. See ek9 -h E07560 for details."},{"error":"E07560","correct":"cat items | head 3 | collect as List of String","incorrect":"cat items | head -1 | collect as List of String","explanation":"Head, tail, and skip require a positive integer greater than zero. Negative values like -1 are rejected at compile time. Use a positive count value. See ek9 -h E07560 for details."},{"error":"E07560","correct":"cat items | head 3 | collect as List of String","incorrect":"cat items | head \"3\" | collect as List of String","explanation":"Head, tail, and skip require an Integer literal, not a String. Passing a string like \"3\" instead of the integer 3 triggers E07560. EK9 does not implicitly convert strings to integers. See ek9 -h E07560 for details."}],"companions":[]}
{"id":126,"category":"Collections and Data Structures","question":"How do I choose the right collection type in EK9?","url":"https://ek9.io/qa/QA0126.html","alternatePhrasings":["When should I use List vs Dict vs PriorityQueue in EK9?","What collection types are available in EK9?","How do I decide between List, Dict, Optional, and Result in EK9?"],"answer":"EK9 provides five main collection and container types. Each serves a distinct purpose.\n\nLIST\nOrdered, indexed collection of elements:\n  items <- [1, 2, 3]\nUse when: you need ordered sequences, indexed access, iteration, stream pipelines.\n\nDICT\nKey-value lookup collection:\n  ages <- {\"Alice\": 30, \"Bob\": 25}\nUse when: you need fast lookup by key, associative data, configuration maps.\n\nPRIORITYQUEUE\nBounded, automatically sorted collection:\n  top <- PriorityQueue(item).withComparator(comp).withSize(5)\nUse when: you need the top-N items, maintaining sorted order with a size limit.\n\nOPTIONAL\nContains zero or one value:\n  name <- Optional(\"Alice\")\nUse when: a value may or may not exist, safe null alternative.\n\nRESULT\nContains either a success value or an error:\n  result <- Result(value) or Result(error)\nUse when: an operation can fail and you want to handle both cases explicitly.\n\nDECISION GUIDE\nNeed a sequence of items?           -> List\nNeed key-value lookup?              -> Dict\nNeed top-N with automatic ordering? -> PriorityQueue\nMight have zero or one value?       -> Optional\nOperation might succeed or fail?    -> Result\n\nSee Q45 for List details. See Q46 for Dict details. See Q47 for Optional. See Q48 for Result. See Q121 for PriorityQueue. See Q129 for safe Dict key access. See Q130 for mutating vs non-mutating operators on collections. See Q182 for collection empty check.","ek9Example":"defines module qa.collections.choosing\n\n  defines program\n\n    ChoosingCollectionDemo()\n      stdout <- Stdout()\n\n      // === LIST: ORDERED SEQUENCE ===\n\n      tasks <- [\"write code\", \"run tests\", \"deploy\"]\n      tasks += \"review\"\n      stdout.println(`Tasks: ${tasks}`)\n\n      // === DICT: KEY-VALUE LOOKUP ===\n\n      config <- {\"host\": \"localhost\", \"port\": \"8080\"}\n      host <- config.getOrDefault(\"host\", \"unknown\")\n      stdout.println(`Host: ${host}`)\n\n      // === PRIORITYQUEUE: TOP-N ===\n\n      comparator <- () extends Comparator of Integer as pure function (r:=? t1 <=> t2)\n      scores <- PriorityQueue(95).withComparator(comparator).withSize(3)\n      scores += 87\n      scores += 92\n      scores += 78\n      scores += 99\n      topScores <- scores.list()\n      stdout.println(`Top 3 scores: ${topScores}`)\n\n      // === OPTIONAL: MAYBE A VALUE ===\n\n      name <- Optional(\"Alice\")\n      if name?\n        stdout.println(`Name: ${name}`)\n\n      // === RESULT: SUCCESS OR ERROR ===\n\n      result <- Result() of (String, Integer)\n      if result?\n        stdout.println(`Got: ${result}`)\n      else\n        stdout.println(\"No result\")","migrationContext":"Java: ArrayList, HashMap, PriorityQueue, Optional, no built-in Result. Python: list, dict, no PriorityQueue built-in (heapq module), no Optional. JavaScript: Array, Map/Object, no PriorityQueue/Optional/Result. Rust: Vec, HashMap, BinaryHeap, Option, Result. Go: slices, maps, no generics until 1.18, no Option/Result. EK9: List, Dict, PriorityQueue, Optional, Result all generic and type-safe with consistent operator syntax.","keywords":["absent","choose","collection","container","data-structure","decision","dict","error","guard","list","ok","optional","priority","result","safe","type"],"primaryTopics":["choose collection","which collection","list vs dict"],"typicalErrors":[{"error":"E50060","correct":"host <- config.getOrDefault(\"host\", \"unknown\")","incorrect":"host <- config.get(\"host\")","explanation":"EK9 Dict has no get() method. Use getOrDefault(key, default) which always returns a usable value. See ek9 -h E50060 for details."},{"error":"E50060","correct":"tasks += \"review\"","incorrect":"tasks.add(\"review\")","explanation":"EK9 List has no .add() method. Use the += operator to add elements. See ek9 -h E50060 for details."},{"error":"E50060","correct":"topScores <- scores.list()","incorrect":"topScores <- scores.toArray()","explanation":"PriorityQueue has no .toArray() method. Use .list() to get entries as a sorted List. See ek9 -h E50060 for details."},{"error":"E50060","correct":"stdout.println(`Tasks: ${tasks}`)","incorrect":"stdout.println(tasks.toString())","explanation":"EK9 does not have toString(). Use string interpolation or the $ operator. See ek9 -h E50060 for details."},{"error":"E50060","correct":"stdout.println(`Host: ${host}`)","incorrect":"stdout.println(host.toString())","explanation":"EK9 does not have toString(). Use string interpolation or the $ operator. See ek9 -h E50060 for details."}],"companions":[]}
{"id":127,"category":"Collections and Data Structures","question":"How does collection type inference work in EK9?","url":"https://ek9.io/qa/QA0127.html","alternatePhrasings":["How does EK9 determine the type of a list literal?","What happens when list elements have different types in EK9?","How does EK9 infer Dict literal types?"],"answer":"EK9 infers collection types from their literal elements. The compiler determines the common type from all elements in the literal.\n\nFIRST-ELEMENT RULE\nThe first element establishes the base type:\n  numbers <- [1, 2, 3]            infers List of Integer\n  names <- [\"Alice\", \"Bob\"]       infers List of String\n  flags <- [true, false, true]    infers List of Boolean\n\nTYPE COERCION IN LITERALS\nWhen elements have compatible types, the compiler promotes to the wider type:\n  mixed <- [1, 2.0, 3]            infers List of Float (Integer promotes to Float)\nThe promote (#^) operator handles type widening automatically.\n\nEXPLICIT TYPING WHEN NEEDED\nFor empty collections, specify the type explicitly:\n  emptyList <- List() of String\n  emptyDict <- Dict() of (String, Integer)\nThe compiler cannot infer types from zero elements.\n\nDICT LITERAL INFERENCE\nDict literals infer both key and value types:\n  ages <- {\"Alice\": 30, \"Bob\": 25}   infers Dict of (String, Integer)\n  scores <- {1: 95.0, 2: 87.5}       infers Dict of (Integer, Float)\n\nSINGLE-ELEMENT CONSTRUCTION\nSingle-element constructors use the element type:\n  single <- List(42)           creates List of Integer with one element\n  opt <- Optional(\"hello\")    creates Optional of String\n\nSee Q45 for List basics. See Q46 for Dict basics. See Q27 for static typing. See Q24 for type conversion and promotion. See Q128 for why collection types are closed by design.\n\nSee Q23 for basic types. See Q45 for list.","ek9Example":"defines module qa.collections.typeinference\n\n  defines program\n\n    TypeInferenceDemo()\n      stdout <- Stdout()\n\n      // === INFERRED FROM FIRST ELEMENT ===\n\n      numbers <- [1, 2, 3, 4, 5]\n      stdout.println(`Integers: ${numbers}`)\n\n      names <- [\"Alice\", \"Bob\", \"Charlie\"]\n      stdout.println(`Strings: ${names}`)\n\n      flags <- [true, false, true]\n      stdout.println(`Booleans: ${flags}`)\n\n      // === TYPE PROMOTION ===\n\n      floats <- [1.0, 2.5, 3.7]\n      stdout.println(`Floats: ${floats}`)\n\n      // === EXPLICIT TYPING FOR EMPTY ===\n\n      emptyStrings <- List() of String\n      stdout.println(`Empty strings: ${emptyStrings}`)\n\n      emptyDict <- Dict() of (String, Integer)\n      stdout.println(`Empty dict: ${emptyDict}`)\n\n      // === DICT LITERAL INFERENCE ===\n\n      ages <- {\"Alice\": 30, \"Bob\": 25, \"Charlie\": 35}\n      stdout.println(`Ages: ${ages}`)\n\n      // === SINGLE ELEMENT CONSTRUCTION ===\n\n      singleList <- List(42)\n      stdout.println(`Single: ${singleList}`)\n\n      singleOpt <- Optional(\"hello\")\n      stdout.println(`Optional: ${singleOpt}`)","migrationContext":"Java: diamond operator <> since Java 7, var since Java 10, List.of() infers types. Python: dynamically typed, no inference needed. JavaScript: dynamically typed. Rust: turbofish ::<T> when inference fails, Vec::new() needs type annotation or usage context. Go: no generics until 1.18, type inference from assignment. Kotlin: type inference from literal elements. EK9: first-element inference, automatic promotion for compatible types, explicit typing for empty collections.","keywords":["automatic","coercion","collection","data-structure","dict","element","generic","inference","list","literal","promote","type"],"primaryTopics":[],"typicalErrors":[{"error":"E50001","correct":"emptyStrings <- List() of String","incorrect":"emptyStrings <- ArrayList()","explanation":"ArrayList is a Java type that does not exist in EK9. Use 'List() of String' for an empty typed list. EK9 has its own collection types. See ek9 -h E50001 for details."},{"error":"E50001","correct":"ages <- {\"Alice\": 30, \"Bob\": 25, \"Charlie\": 35}","incorrect":"ages <- HashMap()","explanation":"HashMap is a Java type that does not exist in EK9. Use Dict literal syntax with curly braces or 'Dict() of (K, V)' for empty dicts. See ek9 -h E50001 for details."},{"error":"E06010","correct":"singleOpt <- Optional(\"hello\")","incorrect":"singleOpt <- Optional()","explanation":"Optional, List, Dict, and Result are generic types that require type parameters. Use 'Optional() of String' for explicit typing, or pass a value like 'Optional(42)' to let the compiler infer the type. See ek9 -h E06010 for details."},{"error":"E06010","correct":"emptyDict <- Dict() of (String, Integer)","incorrect":"emptyDict <- Dict()","explanation":"Dict requires two type parameters for key and value types. Use 'Dict() of (String, Integer)' or a dict literal like '{\"key\": 42}' which infers types from the elements. See ek9 -h E06010 for details."},{"error":"E50001","correct":"singleList <- List(42)","incorrect":"singleList <- Vector(42)","explanation":"Vector is a Java/C++ type that does not exist in EK9. Use List for ordered sequences. EK9 has its own collection types: List, Dict, PriorityQueue, Optional, and Result. See ek9 -h E50001 for details."},{"error":"E50001","correct":"floats <- [1.0, 2.5, 3.7]","incorrect":"floats <- LinkedList(1.0, 2.5, 3.7)","explanation":"LinkedList is a Java type that does not exist in EK9. Use list literal syntax [1.0, 2.5, 3.7] or List() of Float. See ek9 -h E50001 for details."},{"error":"E06010","correct":"emptyStrings <- List() of String","incorrect":"emptyStrings <- List()","explanation":"List is a generic type that requires a type parameter. Use 'List() of String' for explicit typing, or pass a value like 'List(42)' to let the compiler infer the type. See ek9 -h E06010 for details."},{"error":"E50001","correct":"numbers <- [1, 2, 3, 4, 5]","incorrect":"numbers <- Array(1, 2, 3, 4, 5)","explanation":"Array is not an EK9 type. Use list literal syntax [1, 2, 3, 4, 5] for creating a List of Integer. See ek9 -h E50001 for details."}],"companions":[]}
{"id":128,"category":"Collections and Data Structures","question":"Why can't I extend List or Dict in EK9?","url":"https://ek9.io/qa/QA0128.html","alternatePhrasings":["Why are collection types closed in EK9?","How do I add custom behavior to a List in EK9?","What is the alternative to subclassing collections in EK9?"],"answer":"EK9 collection types (List, Dict, PriorityQueue, Optional, Result) are closed by default and cannot be extended. This is a deliberate design decision for semantic integrity.\n\nCLOSED BY DESIGN\nAttempting to extend List or Dict produces a compile error:\n  MyList extends List of String    Error: not open to be extended\nThis applies to all built-in generic collection types.\n\nWHY CLOSED\nJava's open ArrayList and HashMap are widely considered a design mistake:\n  Subclassing mixes collection behavior with application logic\n  Fragile base class problem: internal changes break subclasses\n  Liskov substitution violations when overriding methods\nModern languages (Swift, Rust, Kotlin) avoid this pattern.\n\nCOMPOSITION ALTERNATIVE\nWrap the collection as a private field:\n  ValidatedList\n    items as List of String: List() of String\n    add(item)\n      if isValid(item)\n        items += item\nThis keeps collection behavior separate from your domain logic.\n\nDELEGATION PATTERN\nExpose only the operations that make sense for your domain:\n  size() <- length items\n  get(index) <- items.getOrDefault(index, \"\")\nDon't expose the full collection API if your type has different semantics.\n\nSee Q101 for why types are closed by default. See Q109 for composition over inheritance. See Q45 for List basics. See Q46 for Dict basics. See Q130 for mutating vs non-mutating operators on collections. See Q194 for defining generic classes. See Q212 for composition over inheritance pattern.","ek9Example":"defines module qa.collections.closedcollections\n\n  defines class\n\n    TaskList\n      items as List of String: List() of String\n\n      add()\n        -> task as String\n        if task? and not (items contains task)\n          items += task\n\n      remove()\n        -> task as String\n        items -= task\n\n      size() as pure\n        <- rtn as Integer: length items\n\n      all() as pure\n        <- rtn as List of String: items\n\n      operator $ as pure\n        <- rtn as String: $items\n\n      override operator ? as pure\n        <- rtn as Boolean: items?\n\n  defines program\n\n    ClosedCollectionsDemo()\n      stdout <- Stdout()\n\n      // === COMPOSITION PATTERN ===\n\n      tasks <- TaskList()\n      tasks.add(\"Write code\")\n      tasks.add(\"Run tests\")\n      tasks.add(\"Deploy\")\n\n      // Duplicate is rejected by our add logic\n      tasks.add(\"Run tests\")\n\n      stdout.println(`Tasks (${tasks.size()}): ${tasks}`)\n\n      // Safe removal\n      tasks.remove(\"Deploy\")\n      stdout.println(`After remove: ${tasks}`)\n\n      // Get a copy of internal list\n      allTasks <- tasks.all()\n      stdout.println(`All: ${allTasks}`)","migrationContext":"Java: ArrayList and HashMap are open (considered design mistake). Python: can extend list and dict freely (mixin pattern). JavaScript: can extend Array. Rust: no inheritance, composition required. Go: no inheritance, embed structs. Kotlin: classes final by default (like EK9). EK9: all collection types closed, use composition and delegation instead of inheritance.","keywords":["closed","collection","composition","data-structure","delegation","design","dict","exhaustive","extend","inherit","list","migrate","sealed","wrap"],"primaryTopics":[],"typicalErrors":[{"error":"E01073","correct":"if task? and not (items contains task)","incorrect":"if task <> null","explanation":"EK9 does not have null. The keyword is deliberately excluded. Use the ? operator to check if a variable is set. 'task?' checks tri-state (set/unset), replacing Java-style null checks. See ek9 -h E01073 for details."},{"error":"E06180","correct":"allTasks <- tasks.all()","incorrect":"allTasks <- tasks.items","explanation":"Class fields are always private in EK9. The 'items' field inside TaskList cannot be accessed directly — this is why composition works. You control the API through public methods like all(). See ek9 -h E06180 for details."},{"error":"E50060","correct":"tasks.add(\"Write code\")","incorrect":"tasks.push(\"Write code\")","explanation":"TaskList has no .push() method. The class defines 'add()' as its public API. With composition, you name methods to match your domain. See ek9 -h E50060 for details."},{"error":"E50060","correct":"stdout.println(`Tasks (${tasks.size()}): ${tasks}`)","incorrect":"stdout.println(tasks.toString())","explanation":"EK9 does not have toString(). Use string interpolation or the $ operator. TaskList defines operator $ for string conversion. See ek9 -h E50060 for details."}],"companions":[]}
{"id":129,"category":"Collections and Data Structures","question":"What happens when I access a Dict key that doesn't exist in EK9?","url":"https://ek9.io/qa/QA0129.html","alternatePhrasings":["How do I safely get a value from a Dict in EK9?","Does EK9 throw exceptions for missing dictionary keys?","How do I check if a key exists in a Dict in EK9?"],"answer":"EK9 Dict uses safe access patterns instead of throwing exceptions for missing keys. This eliminates KeyNotFoundException and NullPointerException patterns.\n\nNO EXCEPTIONS ON MISSING KEYS\nEK9 never throws exceptions for missing keys. Instead, getOrDefault always returns a value, and contains checks for key existence.\n\nGETORDEFAULT PATTERN\nProvide a fallback value for missing keys:\n  value <- config.getOrDefault(\"host\", \"localhost\")\nIf \"host\" exists, returns its value. If missing, returns \"localhost\". Always returns a set value.\n\nCONTAINS CHECK\nCheck if a key exists before accessing:\n  if config contains \"port\"\n    stdout.println(\"Port is configured\")\nThe contains operator uses the key type for lookup.\n\nCOMBINING CONTAINS AND GETORDEFAULT\nUse contains when you need conditional logic based on key existence:\n  if config contains \"debug\"\n    debug <- config.getOrDefault(\"debug\", \"false\")\n    stdout.println(debug)\nUse getOrDefault directly when you always want a value.\n\nSAFE ITERATION\nfor-in on a Dict iterates over DictEntry pairs:\n  for entry in config\n    stdout.println($entry)\nThis always works safely, even on an empty Dict.\n\nSee Q46 for Dict basics. See Q90 for Dict operations. See Q29 for unset variables and tri-state semantics. See Q126 for choosing the right collection type. See Q139 for EK9's error handling philosophy (safe returns vs exceptions). See Q260 for Dict key type requirements.","ek9Example":"defines module qa.collections.missingdictkey\n\n  defines program\n\n    MissingDictKeyDemo()\n      stdout <- Stdout()\n\n      config <- {\"host\": \"localhost\", \"port\": \"8080\", \"debug\": \"true\"}\n\n      // === GETORDEFAULT ===\n\n      host <- config.getOrDefault(\"host\", \"unknown\")\n      stdout.println(`Host: ${host}`)\n\n      // Missing key returns the default\n      timeout <- config.getOrDefault(\"timeout\", \"30\")\n      stdout.println(`Timeout: ${timeout}`)\n\n      // === CONTAINS CHECK ===\n\n      if config contains \"port\"\n        stdout.println(\"Port is configured\")\n\n      if not (config contains \"missing\")\n        stdout.println(\"Missing key not found\")\n\n      // === GETORDEFAULT WITH ISSET CHECK ===\n\n      debug <- config.getOrDefault(\"debug\", \"false\")\n      stdout.println(`Debug: ${debug}`)\n\n      // Using contains for existence check\n      if config contains \"debug\"\n        stdout.println(\"Debug is configured\")\n\n      if not (config contains \"nonexistent\")\n        stdout.println(\"Nonexistent key not in dict\")\n\n      // === SAFE ITERATION ===\n\n      for entry in config\n        stdout.println(`Entry: ${entry}`)\n\n      // === EMPTY DICT IS SAFE ===\n\n      emptyDict <- Dict() of (String, String)\n      noValue <- emptyDict.getOrDefault(\"key\", \"default\")\n      stdout.println(`From empty: ${noValue}`)","migrationContext":"Java: map.get() returns null, map.getOrDefault() since Java 8, map.containsKey(). Python: dict[key] throws KeyError, dict.get(key, default) is safe. JavaScript: obj[key] returns undefined. Rust: map.get() returns Option<&V>. Go: val, ok := map[key] two-value return. EK9: getOrDefault always returns a value, contains checks existence, safe iteration with for-in, no exceptions thrown.","keywords":["access","collection","contains","data-structure","default","dict","exception","getOrDefault","key","lookup","missing","safe"],"primaryTopics":[],"typicalErrors":[{"error":"E50060","correct":"host <- config.getOrDefault(\"host\", \"unknown\")","incorrect":"host <- config.get(\"host\")","explanation":"EK9 Dict has no get() method. Use getOrDefault(key, default) which always returns a value, eliminating null/missing-key exceptions. See ek9 -h E50060 for details."},{"error":"E50060","correct":"if config contains \"port\"","incorrect":"if config.containsKey(\"port\")","explanation":"EK9 uses the 'contains' operator for key existence checks, not a containsKey() method. See ek9 -h E50060 for details."},{"error":"E50060","correct":"timeout <- config.getOrDefault(\"timeout\", \"30\")","incorrect":"timeout <- config.get(\"timeout\")","explanation":"EK9 Dict has no get() method. Use getOrDefault(key, default) which always returns a value. See ek9 -h E50060 for details."},{"error":"E07620","correct":"if config contains \"port\"","incorrect":"if config contains 42","explanation":"The contains operator checks for key existence using the Dict's key type. Passing an Integer to a Dict of (String, String) fails because the contains operator is not defined for that type combination. Use the correct key type. See ek9 -h E07620 for details."},{"error":"E50060","correct":"debug <- config.getOrDefault(\"debug\", \"false\")","incorrect":"debug <- config.getOrDefault(\"debug\")","explanation":"getOrDefault requires two arguments: the key and a default value. Omitting the default value causes a parameter mismatch. See ek9 -h E50060 for details."},{"error":"E50060","correct":"noValue <- emptyDict.getOrDefault(\"key\", \"default\")","incorrect":"noValue <- emptyDict.getOrDefault(42, \"default\")","explanation":"The first argument to getOrDefault must match the Dict's key type. Passing an Integer key to a Dict of (String, String) causes a parameter type mismatch. See ek9 -h E50060 for details."}],"companions":[]}
{"id":130,"category":"Collections and Data Structures","question":"Are collections mutable or immutable in EK9?","url":"https://ek9.io/qa/QA0130.html","alternatePhrasings":["How do mutating and non-mutating operators differ on EK9 collections?","Can I modify a list in place in EK9?","What is the difference between += and + for lists in EK9?"],"answer":"EK9 collections are mutable by default. The language distinguishes between mutating operators (modify in place) and non-mutating operators (create new copies).\n\nMUTATING OPERATORS\nThese modify the collection in place:\n  list += item       appends item to this list\n  list -= item       removes item from this list\n  dict += entry      adds entry to this dict\n  dict -= key        removes key from this dict\nThe original collection is changed.\n\nNON-MUTATING OPERATORS\nThese create new collections, leaving the original unchanged:\n  newList <- list + item    new list with item appended\n  newList <- list - item    new list without item\n  newDict <- dict + entry   new dict with entry added\nThe original collection is not modified.\n\nWHEN TO USE EACH\nMutating (+=, -=): building up a collection step by step, performance-sensitive code.\nNon-mutating (+, -): functional style, preserving original data, pipeline transforms.\n\nPURE FUNCTIONS AND COLLECTIONS\nPure functions cannot mutate their parameters. If a pure function receives a collection, it can only use non-mutating operators:\n  process() as pure\n    -> items as List of String\n    <- rtn as List of String: items + \"extra\"\nThe + operator creates a new list. The original items list is untouched.\n\nSee Q45 for List basics. See Q88 for List operations. See Q54 for pure functions. See Q96 for operator overloading. See Q122 for collect as aggregation with operator |. See Q128 for why collection types are closed. See Q141 for constant immutability and copy-on-access protection.","ek9Example":"defines module qa.collections.mutableimmutable\n\n  defines function\n\n    addSuffix() as pure\n      ->\n        items as List of String\n        suffix as String\n      <-\n        rtn as List of String: items + suffix\n\n  defines program\n\n    MutableImmutableDemo()\n      stdout <- Stdout()\n\n      // === MUTATING: += MODIFIES IN PLACE ===\n\n      fruits <- List() of String\n      fruits += \"apple\"\n      fruits += \"banana\"\n      fruits += \"cherry\"\n      stdout.println(`After +=: ${fruits}`)\n\n      // === NON-MUTATING: + CREATES NEW ===\n\n      moreFruits <- fruits + \"date\"\n      stdout.println(`Original: ${fruits}`)\n      stdout.println(`New list: ${moreFruits}`)\n\n      // === MUTATING: -= REMOVES IN PLACE ===\n\n      fruits -= \"banana\"\n      stdout.println(`After -=: ${fruits}`)\n\n      // === NON-MUTATING: - CREATES NEW ===\n\n      lessFruits <- fruits - \"cherry\"\n      stdout.println(`Original: ${fruits}`)\n      stdout.println(`Without cherry: ${lessFruits}`)\n\n      // === DICT MUTATING ===\n\n      config <- {\"host\": \"localhost\"}\n      config += DictEntry(\"port\", \"8080\")\n      stdout.println(`Config: ${config}`)\n\n      // === PURE FUNCTION WITH COLLECTION ===\n\n      result <- addSuffix(fruits, \"!\")\n      stdout.println(`Pure result: ${result}`)\n      stdout.println(`Original unchanged: ${fruits}`)","migrationContext":"Java: Collections.unmodifiableList() for immutable wrappers, List.of() for immutable since Java 9, ArrayList mutable. Python: lists mutable, tuples immutable, frozenset immutable. JavaScript: no built-in immutable, use spread [...arr, item] or Object.freeze(). Rust: Vec mutable by default, shared references prevent mutation. Go: slices mutable, no immutable variant. EK9: collections mutable by default, += mutates, + creates new, pure functions enforce read-only through language rules.","keywords":["collection","copy","data-structure","immutable","list","minus","modify","mutable","mutating","operator","plus","pure","side-effect"],"primaryTopics":[],"typicalErrors":[{"error":"E50060","correct":"fruits += \"apple\"","incorrect":"fruits.add(\"apple\")","explanation":"EK9 uses the += operator to add items to a list, not an add() method. The operator syntax is consistent across all collection types. See ek9 -h E50060 for details."},{"error":"E50060","correct":"fruits -= \"banana\"","incorrect":"fruits.remove(\"banana\")","explanation":"EK9 uses the -= operator to remove items from a list, not a remove() method. See ek9 -h E50060 for details."},{"error":"E50060","correct":"fruits += \"cherry\"","incorrect":"fruits.append(\"cherry\")","explanation":"EK9 List has no append() method. Use the += operator to add items. EK9 uses consistent operator syntax across all collection types rather than named methods. See ek9 -h E50060 for details."},{"error":"E50060","correct":"config += DictEntry(\"port\", \"8080\")","incorrect":"config.put(\"port\", \"8080\")","explanation":"EK9 Dict has no put() method. Use the += operator with DictEntry to add entries. See ek9 -h E50060 for details."}],"companions":[]}
{"id":131,"category":"Collections and Data Structures","question":"What is the EK9 equivalent of Python list comprehensions?","url":"https://ek9.io/qa/QA0131.html","alternatePhrasings":["How do I translate Python list comprehensions to EK9?","How do I filter and transform lists in EK9 like Python?","What replaces [x for x in list if cond] in EK9?"],"answer":"Python list comprehensions translate to EK9 stream pipelines using cat | filter | map | collect. The pipe syntax is explicit about each operation stage.\n\nBASIC FILTERING\nPython: [x for x in items if x > 0]\nEK9:    cat items | filter by isPositive | collect as List of Integer\nDefine the predicate as a named pure function.\n\nBASIC MAPPING\nPython: [f(x) for x in items]\nEK9:    cat items | map with transform | collect as List of String\nDefine the transform as a named pure function.\n\nFILTER THEN MAP\nPython: [f(x) for x in items if pred(x)]\nEK9:    cat items | filter by pred | map with transform | collect as List of String\nPipeline stages chain naturally, filter first then map.\n\nREDUCTION\nPython: sum(x for x in items if x > 0)\nEK9:    cat items | filter by isPositive | collect as Integer\ncollect as Integer sums the stream.\n\nSORT AND LIMIT\nPython: sorted(items)[:5]\nEK9:    cat items | sort | head 5 | collect as List of Integer\nhead replaces Python's slice notation for taking first N.\n\nNESTED COMPREHENSIONS\nPython: [x for sublist in nested for x in sublist]\nEK9:    cat nested | flatten | collect as List of String\nflatten replaces nested for-loops in comprehensions.\n\nSee Q89 for basic stream pipelines. See Q120 for sorting. See Q124 for grouping. See Q125 for head/tail/skip.","ek9Example":"defines module qa.collections.pythoncomprehensions\n\n  defines function\n\n    isPositive() as pure\n      -> num as Integer\n      <- rtn as Boolean: num > 0\n\n    doubleIt() as pure\n      -> num as Integer\n      <- rtn as Integer: num * 2\n\n    intToString() as pure\n      -> num as Integer\n      <- rtn as String: $num\n\n  defines program\n\n    PythonComprehensionsDemo()\n      stdout <- Stdout()\n\n      numbers <- [-2, -1, 0, 1, 2, 3, 4, 5]\n\n      // === FILTERING ===\n      // Python: [x for x in numbers if x > 0]\n\n      positives <- cat numbers | filter by isPositive | collect as List of Integer\n      stdout.println(`Positives: ${positives}`)\n\n      // === MAPPING ===\n      // Python: [x * 2 for x in numbers]\n\n      doubled <- cat numbers | map with doubleIt | collect as List of Integer\n      stdout.println(`Doubled: ${doubled}`)\n\n      // === FILTER THEN MAP ===\n      // Python: [x * 2 for x in numbers if x > 0]\n\n      doubledPositives <- cat numbers | filter by isPositive | map with doubleIt | collect as List of Integer\n      stdout.println(`Doubled positives: ${doubledPositives}`)\n\n      // === REDUCTION ===\n      // Python: sum(x for x in numbers if x > 0)\n\n      total <- cat numbers | filter by isPositive | collect as Integer\n      stdout.println(`Sum of positives: ${total}`)\n\n      // === SORT AND LIMIT ===\n      // Python: sorted(numbers)[:3]\n\n      topThree <- cat numbers | sort | head 3 | collect as List of Integer\n      stdout.println(`First 3 sorted: ${topThree}`)\n\n      // === NESTED FLATTEN ===\n      // Python: [x for sublist in nested for x in sublist]\n\n      nested <- [[\"a\", \"b\"], [\"c\", \"d\"], [\"e\", \"f\"]]\n      flat <- cat nested | flatten | collect as List of String\n      stdout.println(`Flattened: ${flat}`)","migrationContext":"Python: [expr for var in iterable if cond] comprehension syntax, generator expressions with (), dict comprehensions {k: v for ...}. EK9: cat source | filter by pred | map with func | collect as Type pipeline. Key differences: EK9 requires named functions (not lambdas), explicit pipeline stages, strongly typed collection results. Both require the data source first, then operations.","keywords":["collection","comprehension","data-structure","equivalent","filter","list","map","migration","pipeline","python","stream","translate"],"primaryTopics":[],"typicalErrors":[{"error":"E50001","correct":"isPositive() as pure\n      -> num as Integer\n      <- rtn as Boolean: num > 0","incorrect":"badFilter() as pure\n      -> num as Integer\n      <- rtn as Integer: num","explanation":"The 'filter by' pipeline stage requires a function that returns Boolean. If the filter function returns Integer instead of Boolean, E50001 is triggered — must return a Boolean. In Python, any truthy value works as a filter. In EK9, the filter function must explicitly return Boolean. See ek9 -h E50001 for details."},{"error":"E07830","correct":"cat numbers | filter by isPositive | collect as List of Integer","incorrect":"cat numbers | filter by isPositive | collect as List of String","explanation":"The collect type must match the pipeline's element type. An Integer pipeline cannot collect into List of String because there is no pipe operator between Integer and String. E07830 is triggered — unable to find a pipe operator for type. In Python, type mismatches fail at runtime. In EK9, the compiler catches them. See ek9 -h E07830 for details."},{"error":"E07830","correct":"positives <- cat numbers | filter by isPositive | collect as List of Integer","incorrect":"positives <- cat numbers | filter by isPositive | collect as List of String","explanation":"The collect type must match the pipeline's element type. An Integer pipeline cannot collect into List of String. See ek9 -h E07830 for details."},{"error":"E50060","correct":"doubled <- cat numbers | map with doubleIt | collect as List of Integer","incorrect":"doubled <- numbers.stream().map(doubleIt).collect()","explanation":"EK9 has no .stream() or .map() methods. Use pipe syntax: cat source | map with func | collect as Type. See ek9 -h E50060 for details."},{"error":"E07830","correct":"doubledPositives <- cat numbers | filter by isPositive | map with doubleIt | collect as List of Integer","incorrect":"doubledPositives <- cat numbers | filter by isPositive | map with doubleIt | collect as List of String","explanation":"The collect type must match the pipeline's output element type. After map with doubleIt, the stream contains Integer, not String. See ek9 -h E07830 for details."},{"error":"E07830","correct":"topThree <- cat numbers | sort | head 3 | collect as List of Integer","incorrect":"topThree <- cat numbers | sort | head 3 | collect as List of String","explanation":"The collect type must match the pipeline's element type. An Integer pipeline cannot collect into List of String. See ek9 -h E07830 for details."},{"error":"E50060","correct":"flat <- cat nested | flatten | collect as List of String","incorrect":"flat <- nested.flatMap().collect()","explanation":"EK9 has no Java-style flatMap() or collect() methods on List. Use | flatten in a pipeline to flatten nested lists. See ek9 -h E50060 for details."},{"error":"E07830","correct":"flat <- cat nested | flatten | collect as List of String","incorrect":"flat <- cat nested | flatten | collect as List of Integer","explanation":"The collect type must match the pipeline's element type. A String pipeline cannot collect into List of Integer. See ek9 -h E07830 for details."}],"companions":[]}
{"id":132,"category":"Collections and Data Structures","question":"What is the EK9 equivalent of the Java Collections Framework?","url":"https://ek9.io/qa/QA0132.html","alternatePhrasings":["How do I translate Java ArrayList and HashMap code to EK9?","What replaces Java Stream API in EK9?","How do Java collection patterns map to EK9?","What replaces Java ArrayList in EK9?"],"answer":"EK9 provides equivalent collection types to Java's Collections Framework, but with simpler syntax and unified operator patterns.\n\nARRAYLIST TO LIST\nJava: List<String> names = new ArrayList<>(List.of(\"Alice\", \"Bob\"));\nEK9:  names <- [\"Alice\", \"Bob\"]\nNo angle brackets, no ArrayList/List distinction, literal syntax.\n\nHASHMAP TO DICT\nJava: Map<String, Integer> ages = new HashMap<>(Map.of(\"Alice\", 30));\nEK9:  ages <- {\"Alice\": 30}\nLiteral syntax, no Map.of() or put() calls needed.\n\nPRIORITYQUEUE TO PRIORITYQUEUE\nJava: PriorityQueue<String> pq = new PriorityQueue<>(comparator);\nEK9:  pq <- PriorityQueue(\"first\").withComparator(comparator)\nFluent API with bounded size support via withSize(n).\n\nOPTIONAL TO OPTIONAL\nJava: Optional<String> name = Optional.of(\"Alice\");\nEK9:  name <- Optional(\"Alice\")\nSame concept, simpler syntax, integrated with guard variables.\n\nSTREAM API TO EK9 STREAMS\nJava: list.stream().filter(x -> x > 0).map(x -> x * 2).collect(Collectors.toList());\nEK9:  cat list | filter by isPositive | map with doubleIt | collect as List of Integer\nNamed functions instead of lambdas, pipe syntax instead of method chaining.\n\nCOLLECTORS TO COLLECT AS\nJava: Collectors.groupingBy(), Collectors.summarizingInt(), Collectors.joining()\nEK9:  | group by func, | collect as Stats (custom), | collect as String\nCustom collectors via operator | on records.\n\nKEY DIFFERENCES\nEK9 uses 'of' instead of angle brackets for generics.\nNo null in EK9, tri-state semantics instead.\nOperators (+=, -=, +, -) instead of method calls (add, remove, put).\nAll types closed (no extending List or Dict).\n\nSee Q45 for List. See Q46 for Dict. See Q121 for PriorityQueue. See Q122 for collect as custom aggregation.","ek9Example":"defines module qa.collections.javacollections\n\n  defines function\n\n    isEven() as pure\n      -> num as Integer\n      <- rtn as Boolean: num mod 2 == 0\n\n    doubleIt() as pure\n      -> num as Integer\n      <- rtn as Integer: num * 2\n\n  defines program\n\n    JavaCollectionsDemo()\n      stdout <- Stdout()\n\n      // === ArrayList -> List ===\n      // Java: List<String> names = new ArrayList<>(List.of(\"Alice\", \"Bob\", \"Charlie\"));\n\n      names <- [\"Alice\", \"Bob\", \"Charlie\"]\n      names += \"Dave\"\n      stdout.println(`Names: ${names}`)\n\n      // === HashMap -> Dict ===\n      // Java: Map<String, Integer> ages = new HashMap<>(Map.of(\"Alice\", 30));\n\n      ages <- {\"Alice\": 30, \"Bob\": 25}\n      age <- ages.getOrDefault(\"Alice\", 0)\n      stdout.println(`Alice age: ${age}`)\n\n      // === Stream API -> EK9 Streams ===\n      // Java: numbers.stream().filter(x -> x % 2 == 0).map(x -> x * 2).collect(Collectors.toList());\n\n      numbers <- [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]\n      result <- cat numbers | filter by isEven | map with doubleIt | collect as List of Integer\n      stdout.println(`Doubled evens: ${result}`)\n\n      // === Collectors.summarizingInt -> collect as Integer ===\n      // Java: numbers.stream().mapToInt(x -> x).sum();\n\n      total <- cat numbers | collect as Integer\n      stdout.println(`Sum: ${total}`)\n\n      // === Optional ===\n      // Java: Optional<String> name = Optional.of(\"Alice\");\n\n      optName <- Optional(\"Alice\")\n      if optName?\n        stdout.println(`Got: ${optName}`)\n\n      // === PriorityQueue ===\n      // Java: PriorityQueue<Integer> pq = new PriorityQueue<>(comparator);\n\n      comparator <- () extends Comparator of Integer as pure function (r:=? t1 <=> t2)\n      pq <- PriorityQueue(5).withComparator(comparator)\n      pq += 3\n      pq += 8\n      pq += 1\n      stdout.println(`PQ: ${pq.list()}`)","migrationContext":"Java: ArrayList<T>, HashMap<K,V>, PriorityQueue<T>, Optional<T>, Stream API with Collectors. EK9: List of T, Dict of (K, V), PriorityQueue of T, Optional of T, cat | pipe | collect. Key differences: EK9 literal syntax ([1,2,3] and {k:v}), 'of' instead of <>, operators instead of methods, named functions instead of lambdas, no null.","keywords":["Kotlin","LINQ","Rust","arraylist","collection","collectors","convert","data-structure","equivalent","framework","generic","hashmap","java","migration","replace","stream","translate"],"primaryTopics":[],"typicalErrors":[{"error":"E50060","correct":"names += \"Dave\"","incorrect":"names.add(\"Dave\")","explanation":"EK9 uses the += operator to add items to a List, not the Java-style add() method. See ek9 -h E50060 for details."},{"error":"E50060","correct":"age <- ages.getOrDefault(\"Alice\", 0)","incorrect":"age <- ages.get(\"Alice\")","explanation":"EK9 Dict has no get() method. Use getOrDefault(key, default) for safe access. See ek9 -h E50060 for details."},{"error":"E50060","correct":"cat numbers | filter by isEven | map with doubleIt | collect as List of Integer","incorrect":"numbers.stream().filter(isEven).map(doubleIt).toList()","explanation":"EK9 does not have Java's Stream API. No .stream(), .filter(), .map(), or .toList() methods. Use pipe syntax: cat source | filter by fn | map with fn | collect as Type. See ek9 -h E50060 for details."},{"error":"E50060","correct":"stdout.println(`Names: ${names}`)","incorrect":"stdout.println(names.toString())","explanation":"EK9 does not have toString(). Use string interpolation or the $ operator. See ek9 -h E50060 for details."},{"error":"E50060","correct":"stdout.println(`Doubled evens: ${result}`)","incorrect":"stdout.println(result.toString())","explanation":"EK9 does not have toString(). Use string interpolation or the $ operator. See ek9 -h E50060 for details."}],"companions":[]}
{"id":133,"category":"Collections and Data Structures","question":"How do I use tee and uniq in stream pipelines in EK9?","url":"https://ek9.io/qa/QA0133.html","alternatePhrasings":["How do I capture intermediate stream results in EK9?","How do I remove duplicates from a stream in EK9?","How do I debug a stream pipeline in EK9?","How do I take a side copy of data as it flows through a stream pipeline in EK9?","How do I observe or inspect elements mid-pipeline in EK9?"],"answer":"EK9 provides tee for capturing intermediate pipeline state and uniq for removing duplicate elements. Both are pipeline operations that integrate with cat | collect syntax.\n\nTEE FOR SIDE EFFECTS\nTee copies each element to a variable while passing it through:\n  captured <- List() of String\n  cat items | tee in captured | collect as List of String\nAfter the pipeline, captured contains all items that flowed through that point. Useful for debugging or capturing intermediate state.\n\nTEE FOR DEBUGGING\nInsert tee at any point in a pipeline to observe values:\n  cat items\n    | filter by isValid\n    | tee in afterFilter\n    | map with transform\n    | collect as List of String\nafterFilter shows what passed the filter before transformation.\n\nUNIQ BY HASHCODE\nRemove consecutive duplicates using the #? (hashcode) operator:\n  cat items | sort | uniq | collect as List of String\nSort first to ensure duplicates are adjacent. Uses the element's #? operator for comparison.\n\nUNIQ BY KEY EXTRACTOR\nRemove duplicates based on a specific field:\n  cat items | uniq by extractKey | collect as List of Item\nThe key extractor function determines what counts as a duplicate.\n\nCOMBINING TEE AND UNIQ\nCapture state before deduplication:\n  cat items\n    | sort\n    | tee in beforeUniq\n    | uniq\n    | collect as List of String\nbeforeUniq has all sorted items; the result has duplicates removed.\n\nSee Q89 for basic stream pipelines. See Q96 for hashcode operator (#?). See Q120 for sort (needed before uniq). See Q124 for group by. See Q235 for complete stream operations reference. See Q237 for streams vs loops decision guide.","ek9Example":"defines module qa.collections.teeuniq\n\n  defines function\n\n    firstChar() as pure\n      -> word as String\n      <- rtn as String: $#<word\n\n    longEnough() as pure\n      -> word as String\n      <- rtn <- Boolean()\n      expectedCount <- 4\n      rtn: length word >= expectedCount\n\n  defines program\n\n    TeeUniqDemo()\n      stdout <- Stdout()\n\n      words <- [\"banana\", \"apple\", \"cherry\", \"apple\", \"banana\", \"date\", \"cherry\", \"elderberry\"]\n\n      // === TEE: CAPTURE INTERMEDIATE STATE ===\n\n      afterFilter <- List() of String\n      result <- cat words\n        | filter by longEnough\n        | tee in afterFilter\n        | sort\n        | collect as List of String\n      stdout.println(`After filter (via tee): ${afterFilter}`)\n      stdout.println(`Final sorted: ${result}`)\n\n      // === UNIQ: REMOVE DUPLICATES ===\n\n      // Sort first to make duplicates adjacent\n      unique <- cat words | sort | uniq | collect as List of String\n      stdout.println(`Unique: ${unique}`)\n\n      // === UNIQ BY KEY EXTRACTOR ===\n\n      // One word per first character\n      uniqueByFirst <- cat words | sort | uniq by firstChar | collect as List of String\n      stdout.println(`Unique by first char: ${uniqueByFirst}`)\n\n      // === TEE + UNIQ TOGETHER ===\n\n      beforeUniq <- List() of String\n      deduped <- cat words\n        | sort\n        | tee in beforeUniq\n        | uniq\n        | collect as List of String\n      stdout.println(`Before uniq: ${beforeUniq}`)\n      stdout.println(`After uniq: ${deduped}`)","migrationContext":"Java: stream.peek() for side effects (like tee), stream.distinct() for unique elements. Python: no built-in tee (itertools.tee is different), set() for unique. JavaScript: no built-in pipeline tee, [...new Set(arr)] for unique. Rust: iter.inspect() for tee, iter.dedup() for consecutive dedup (like EK9 uniq). Go: manual implementation for both. EK9: | tee in variable for capture, | uniq for dedup by hashcode, | uniq by func for dedup by key.","keywords":["capture","collection","copy","data-structure","debug","duplicate","effect","inspect","intercept","intermediate","observe","pipeline","side","stream","tap","tee","uniq","unique"],"primaryTopics":[],"typicalErrors":[{"error":"E50060","correct":"unique <- cat words | sort | uniq | collect as List of String","incorrect":"unique <- words.stream().distinct().collect()","explanation":"EK9 has no distinct() or stream() methods. Use | sort | uniq in a pipeline to remove consecutive duplicates by hashcode. See ek9 -h E50060 for details."},{"error":"E07830","correct":"unique <- cat words | sort | uniq | collect as List of String","incorrect":"unique <- cat words | sort | uniq | collect as List of Integer","explanation":"The collect type must match the pipeline's element type. A String pipeline cannot collect into List of Integer. See ek9 -h E07830 for details."},{"error":"E07830","correct":"uniqueByFirst <- cat words | sort | uniq by firstChar | collect as List of String","incorrect":"uniqueByFirst <- cat words | sort | uniq by firstChar | collect as List of Integer","explanation":"The collect type must match the pipeline's element type. After uniq by firstChar, the stream still contains String elements, not Integer. See ek9 -h E07830 for details."},{"error":"E50060","correct":"unique <- cat words | sort | uniq | collect as List of String","incorrect":"unique <- words.stream().distinct().collect()","explanation":"EK9 has no distinct() or stream() methods. Use | sort | uniq in a pipeline to remove consecutive duplicates by hashcode. See ek9 -h E50060 for details."},{"error":"E07830","correct":"| tee in afterFilter\n        | sort\n        | collect as List of String","incorrect":"| tee in afterFilter\n        | sort\n        | collect as List of Integer","explanation":"The collect type must match the pipeline's element type. A String pipeline cannot collect into List of Integer. See ek9 -h E07830 for details."},{"error":"E07830","correct":"| tee in beforeUniq\n        | uniq\n        | collect as List of String","incorrect":"| tee in beforeUniq\n        | uniq\n        | collect as List of Integer","explanation":"The tee and collect must match the pipeline type. After uniq the stream still contains Strings, so collecting into List of Integer fails. See ek9 -h E07830 for details."},{"error":"E50060","correct":"stdout.println(`Unique: ${unique}`)","incorrect":"stdout.println(unique.toString())","explanation":"EK9 List has no toString() method. Use the $ prefix operator or string interpolation for string conversion. See ek9 -h E50060 for details."}],"companions":[]}
{"id":134,"category":"Error Handling and Exceptions","question":"How do I use try/catch to handle exceptions in EK9?","url":"https://ek9.io/qa/QA0134.html","alternatePhrasings":["What is exception handling in EK9?","How does try-catch work in EK9?","What is the EK9 equivalent of Java try-catch?","How do I catch errors in EK9?"],"answer":"EK9 uses try/catch blocks to handle exceptions, similar to Java, Python, and other languages. The catch block binds the exception to a variable using the arrow syntax.\n\nBASIC TRY/CATCH\nWrap code that might throw in a try block, and handle errors in catch:\n  try\n    riskyOperation()\n  catch\n    -> ex as Exception\n    handleError(ex)\nThe catch block only executes if an exception is thrown inside the try block.\n\nEXCEPTION BINDING\nThe catch block declares an exception variable with arrow syntax:\n  catch\n    -> ex as Exception\nThis binds the caught exception to the variable 'ex' for use within the catch body.\n\nACCESSING EXCEPTION INFO\nException provides reason() and the $ operator for string conversion:\n  catch\n    -> ex as Exception\n    stdout.println(ex.reason())\n    stdout.println($ex)\nThe reason() method returns the message. The $ operator converts the full exception to a string.\n\nHANDLE SYNONYM\nEK9 supports 'handle' as a synonym for 'catch'. Both are identical:\n  try\n    riskyOperation()\n  handle\n    -> ex as Exception\n    handleError(ex)\nUse whichever reads better in context.\n\nTRY AS EXPRESSION\nTry can be used as an expression that returns a value:\n  result <- try\n    <- rtn as String: \"default\"\n    rtn: computeValue()\n  catch\n    -> ex as Exception\n    rtn: \"error: \" + $ex\nThe return variable must be declared in the try body.\n\nSee Q78 for guard variables in try blocks. See Q48 for the Result type alternative. See Q135 for try/catch/finally. See Q136 for throwing exceptions. See Q246 for debugging strategies. See Q283 for retry logic without break.\n\nSee Q304 for require preconditions. See Q305 for require vs assert vs throw.\n\nSee Q333 for transaction try-with-resources. See Q338 for partial commit prevention.","ek9Example":"defines module qa.errorhandling.trycatch\n\n  defines function\n\n    riskyDivide()\n      ->\n        a as Integer\n        b as Integer\n      <- rtn <- Integer()\n\n      if b == 0\n        ex <- Exception(\"Division by zero\")\n        throw ex\n      rtn: a / b\n\n  defines program\n\n    TryCatchBasics()\n      stdout <- Stdout()\n\n      // === BASIC TRY/CATCH ===\n\n      try\n        result <- riskyDivide(10, 2)\n        stdout.println(\"Result: \" + $result)\n      catch\n        -> ex as Exception\n        stdout.println(\"Error: \" + $ex)\n\n      // === CATCHING AN EXCEPTION ===\n\n      try\n        result <- riskyDivide(10, 0)\n        stdout.println(\"Result: \" + $result)\n      catch\n        -> ex as Exception\n        stdout.println(\"Caught: \" + ex.reason())\n\n      // === HANDLE SYNONYM ===\n\n      try\n        result <- riskyDivide(5, 0)\n        stdout.println(\"Result: \" + $result)\n      handle\n        -> ex as Exception\n        stdout.println(\"Handled: \" + $ex)\n\n      // === TRY AS EXPRESSION ===\n\n      message <- try\n        <- rtn as String: \"no result\"\n        value <- riskyDivide(20, 4)\n        rtn: \"Computed: \" + $value\n      catch\n        -> ex as Exception\n        rtn: \"Failed: \" + ex.reason()\n\n      stdout.println(message)","migrationContext":"Java: try { ... } catch (Exception e) { ... } with checked/unchecked distinction. Python: try: ... except Exception as e: ... with duck-typed exceptions. Rust: no try/catch, uses Result<T,E> and ? operator. Go: no try/catch, uses error return values. Kotlin: try/catch like Java but all exceptions unchecked. Swift: do { try expr() } catch { error }, throws keyword marks throwing functions, try? converts to optional, try! force-unwraps (crashes on error). EK9: try/catch with arrow binding (-> ex as Exception), 'handle' synonym for 'catch', all exceptions unchecked, try can be an expression.","keywords":["basic","binding","catch","error","exception","handle","migrate","reason","swift","syntax","throw","try"],"primaryTopics":["try catch","exception handling","error handling"],"typicalErrors":[{"error":"E50010","correct":"catch\n        -> ex as Exception","incorrect":"catch\n        ->\n          ex1 as ValidationError\n          ex2 as Exception","explanation":"EK9 catch blocks accept only a single exception parameter. Unlike Java's multi-catch, you cannot list multiple exception types. E50010 is triggered — only a single Exception is supported. Catch the common base type (Exception) and check specific types within the block. See ek9 -h E50010 for details."},{"error":"E04030","correct":"catch\n        -> ex as Exception","incorrect":"catch\n        -> ex as String","explanation":"The catch variable must be an Exception type or a subclass of Exception. Using a non-exception type like String triggers E04030 — type must be of Exception type. Define custom exceptions with 'extends Exception'. See ek9 -h E04030 for details."},{"error":"E04030","correct":"ex <- Exception(\"Division by zero\")\n        throw ex","incorrect":"ex <- \"Division by zero\"\n        throw ex","explanation":"Only Exception types (or subtypes) can be thrown in EK9. Throwing a String triggers E04030 — type must be of Exception type. Wrap the message in an Exception constructor. See ek9 -h E04030 for details."},{"error":"E50060","correct":"stdout.println(\"Caught: \" + ex.reason())","incorrect":"stdout.println(\"Caught: \" + ex.getMessage())","explanation":"EK9 Exception has no getMessage() method. Use reason() to get the exception message. See ek9 -h E50060 for details."},{"error":"E50060","correct":"stdout.println(\"Error: \" + $ex)","incorrect":"stdout.println(ex.toString())","explanation":"EK9 has no toString() method. Use the $ operator or string interpolation for string conversion. See ek9 -h E50060 for details."},{"error":"E50060","correct":"rtn: \"Failed: \" + ex.reason()","incorrect":"rtn: \"Failed: \" + ex.getMessage()","explanation":"EK9 Exception has no getMessage() method. Use reason() to get the exception message. See ek9 -h E50060 for details."},{"error":"E50060","correct":"stdout.println(\"Handled: \" + $ex)","incorrect":"stdout.println(ex.toString())","explanation":"EK9 has no toString() method. Use the $ operator for string conversion. See ek9 -h E50060 for details."}],"companions":[]}
{"id":135,"category":"Error Handling and Exceptions","question":"How does try/catch/finally work in EK9?","url":"https://ek9.io/qa/QA0135.html","alternatePhrasings":["What does finally do in EK9?","How do I guarantee cleanup in EK9?","Does EK9 have try-finally?"],"answer":"The finally block in EK9 guarantees cleanup code runs regardless of whether the try block succeeds, an exception is caught, or a guard is unset.\n\nFULL PATTERN\nThe complete try/catch/finally structure:\n  try\n    riskyOperation()\n  catch\n    -> ex as Exception\n    handleError(ex)\n  finally\n    cleanup()\nThe finally block always runs after try and catch complete.\n\nFINALLY ALWAYS RUNS\nFinally executes in all three scenarios:\n1. Try completes normally (no exception)\n2. Exception thrown and caught\n3. Guard variable is unset (try body skipped)\nThis guarantee makes finally ideal for cleanup operations.\n\nTRY/FINALLY WITHOUT CATCH\nYou can use try/finally without a catch block:\n  try\n    doWork()\n  finally\n    cleanup()\nIf an exception occurs, it propagates after finally runs.\n\nCLEANUP PATTERNS\nCommon uses for finally:\n- Logging completion status\n- Resetting state\n- Releasing resources (though try-with-resources is preferred)\n\nNESTED TRY BLOCKS\nTry blocks can be nested. Each has its own catch and finally:\n  try\n    try\n      innerOperation()\n    catch\n      -> ex as Exception\n      handleInner(ex)\n    finally\n      innerCleanup()\n  finally\n    outerCleanup()\n\nSee Q134 for basic try/catch. See Q137 for try-with-resources (automatic cleanup). See Q78 for guard variables in try blocks.","ek9Example":"defines module qa.errorhandling.trycatchfinally\n\n  defines function\n\n    riskyOperation()\n      -> shouldFail as Boolean\n      <- rtn <- String()\n\n      if shouldFail\n        ex <- Exception(\"Operation failed\")\n        throw ex\n      rtn: \"success\"\n\n  defines program\n\n    TryCatchFinallyDemo()\n      stdout <- Stdout()\n\n      // === FULL PATTERN: try/catch/finally ===\n\n      stdout.println(\"=== Normal execution ===\")\n      try\n        result <- riskyOperation(false)\n        stdout.println(\"Try: \" + result)\n      catch\n        -> ex as Exception\n        stdout.println(\"Catch: \" + $ex)\n      finally\n        stdout.println(\"Finally: always runs\")\n\n      // === EXCEPTION PATH ===\n\n      stdout.println(\"=== Exception path ===\")\n      try\n        result <- riskyOperation(true)\n        stdout.println(\"Try: \" + result)\n      catch\n        -> ex as Exception\n        stdout.println(\"Catch: \" + ex.reason())\n      finally\n        stdout.println(\"Finally: still runs after catch\")\n\n      // === TRY/FINALLY WITHOUT CATCH ===\n\n      stdout.println(\"=== Try/finally without catch ===\")\n      try\n        stdout.println(\"Try: doing work\")\n      finally\n        stdout.println(\"Finally: cleanup without catch\")\n\n      // === NESTED TRY BLOCKS ===\n\n      stdout.println(\"=== Nested try blocks ===\")\n      try\n        try\n          result <- riskyOperation(true)\n          stdout.println(\"Inner try: \" + result)\n        catch\n          -> ex as Exception\n          stdout.println(\"Inner catch: \" + ex.reason())\n        finally\n          stdout.println(\"Inner finally\")\n      finally\n        stdout.println(\"Outer finally\")","migrationContext":"Java: try { } catch { } finally { } with identical guarantee. Python: try: ... except: ... finally: ... same semantics. Rust: no try/finally, uses RAII (Drop trait) for cleanup. Go: defer statement provides cleanup guarantee. Kotlin: try/catch/finally like Java. Swift: defer keyword for cleanup (runs when scope exits, same purpose as finally), no try/finally syntax. EK9: try/catch/finally with same guarantee, also works with guard variables (unset guard skips try body but finally still runs).","keywords":["always","catch","cleanup","defer","error","exception","finally","guarantee","handle","nested","pattern","resource","swift","try"],"primaryTopics":[],"typicalErrors":[{"error":"E50001","correct":"catch\n        -> ex as Exception","incorrect":"catch\n        ->\n          ex1 as Exception\n          ex2 as Exception","explanation":"Catch blocks accept only a single exception parameter. EK9 does not support multi-catch syntax like Java. E50001 — only a single Exception is supported. Use the Exception base type and dispatch inside the catch body. See ek9 -h E50001 for details."},{"error":"E04030","correct":"-> ex as Exception\n        stdout.println(\"Catch: \" + $ex)","incorrect":"-> ex as Integer\n        stdout.println(\"Catch: \" + $ex)","explanation":"The catch parameter must be an Exception type or subtype. Using Integer triggers E04030 — type must be of Exception type. See ek9 -h E04030 for details."},{"error":"E50060","correct":"stdout.println(\"Catch: \" + ex.reason())","incorrect":"stdout.println(\"Catch: \" + ex.getMessage())","explanation":"EK9 Exception has no getMessage() method. Use reason() to get the exception message. See ek9 -h E50060 for details."},{"error":"E04030","correct":"ex <- Exception(\"Operation failed\")\n        throw ex","incorrect":"ex <- \"Operation failed\"\n        throw ex","explanation":"Only Exception types can be thrown in EK9. Throwing a String triggers E04030 — type must be of Exception type. Wrap the message in an Exception constructor. See ek9 -h E04030 for details."},{"error":"E50060","correct":"stdout.println(\"Inner catch: \" + ex.reason())","incorrect":"stdout.println(\"Inner catch: \" + ex.getMessage())","explanation":"EK9 Exception has no getMessage() method. Use reason() for the exception message. See ek9 -h E50060 for details."},{"error":"E50060","correct":"stdout.println(\"Try: \" + result)","incorrect":"stdout.println(result.toString())","explanation":"EK9 has no toString() method. Use string concatenation or interpolation for string conversion. See ek9 -h E50060 for details."}],"companions":[]}
{"id":136,"category":"Error Handling and Exceptions","question":"How do I throw exceptions in EK9?","url":"https://ek9.io/qa/QA0136.html","alternatePhrasings":["How do I raise an error in EK9?","How do I create custom exceptions?","How do I extend Exception in EK9?"],"answer":"EK9 uses the throw keyword to raise exceptions. You can throw the built-in Exception or create custom exception types by extending Exception.\n\nCREATING EXCEPTIONS\nCreate an Exception with a reason string:\n  ex <- Exception(\"Something went wrong\")\nOr with a reason and exit code:\n  ex <- Exception(\"Fatal error\", 1)\nThe exit code is used if the exception propagates to the program entry point.\n\nTHROW KEYWORD\nThrow an exception variable:\n  ex <- Exception(\"Error occurred\")\n  throw ex\nThe throw statement must reference a variable, not an inline expression.\n\nCUSTOM EXCEPTION TYPES\nDefine custom exceptions by extending Exception:\n  ValidationError extends Exception\n    field <- String()\n    ValidationError()\n      ->\n        reason as String\n        field as String\n      super(reason)\n      this.field :=: field\n    field() as pure\n      <- rtn as String: field\n    default operator ?\nCustom exceptions can carry additional context beyond the reason string.\n\nEXCEPTION PROPERTIES\nAll exceptions have:\n  ex.reason()     the error message\n  ex.exitCode()   optional exit code\n  $ex             string representation\n  ex?             isSet check\n\nWHEN TO THROW\nThrow exceptions for truly unexpected, exceptional conditions:\n- Invalid arguments that indicate programmer error\n- System failures (I/O errors, resource unavailability)\n- Broken invariants that should never occur\nFor expected failures (validation, missing data), prefer the Result type instead.\n\nSee Q134 for try/catch. See Q138 for catching specific exception types. See Q139 for choosing between try/catch and Result.\n\nSee Q304 for require preconditions. See Q305 for require vs assert vs throw.","ek9Example":"defines module qa.errorhandling.throwexception\n\n  defines class\n\n    <?-\n      Custom exception for validation errors with field name context.\n    -?>\n    ValidationError extends Exception\n      field <- String()\n\n      ValidationError()\n        ->\n          reason as String\n          field as String\n\n        super(reason)\n        this.field :=: field\n\n      field() as pure\n        <- rtn as String: field\n\n      default operator ?\n\n  defines function\n\n    validateAge()\n      -> age as Integer\n      <- rtn <- String()\n\n      if age < 0\n        ex <- ValidationError(\"Age cannot be negative\", \"age\")\n        throw ex\n      maxAmount <- 150\n      if age > maxAmount\n        ex <- ValidationError(\"Age unreasonably large\", \"age\")\n        throw ex\n      rtn: \"Valid age: \" + $age\n\n  defines program\n\n    ThrowExceptionDemo()\n      stdout <- Stdout()\n\n      // === THROWING BUILT-IN EXCEPTION ===\n\n      try\n        ex <- Exception(\"Something went wrong\")\n        throw ex\n      catch\n        -> ex as Exception\n        stdout.println(\"Caught: \" + ex.reason())\n\n      // === THROWING CUSTOM EXCEPTION ===\n\n      try\n        result <- validateAge(-5)\n        stdout.println(result)\n      catch\n        -> ex as ValidationError\n        stdout.println(`Validation failed on field '${ex.field()}': ${ex.reason()}`)\n\n      // === SUCCESSFUL PATH ===\n\n      try\n        result <- validateAge(25)\n        stdout.println(result)\n      catch\n        -> ex as ValidationError\n        stdout.println(\"Should not reach here\")\n\n      // === EXCEPTION WITH EXIT CODE ===\n\n      try\n        ex <- Exception(\"Fatal startup error\", 1)\n        throw ex\n      catch\n        -> ex as Exception\n        stdout.println(\"Exit code: \" + $ex.exitCode())","migrationContext":"Java: throw new SomeException(\"msg\") with extends Exception or RuntimeException. Python: raise ValueError(\"msg\") with class hierarchy. Rust: no throw, uses Result::Err() or panic!(). Go: no throw, uses error return values. Kotlin: throw SomeException(\"msg\") like Java. Swift: throw SomeError() with Error protocol conformance, throws keyword required on function signatures. EK9: throw ex where ex is a variable (not inline new), custom exceptions use 'extends Exception' with additional fields, default operator ? required.","keywords":["catch","create","custom","error","exception","exitCode","extends","handle","raise","reason","swift","throw"],"primaryTopics":["throw exception","raise exception"],"typicalErrors":[{"error":"E50060","correct":"stdout.println(\"Caught: \" + ex.reason())","incorrect":"stdout.println(\"Caught: \" + ex.getMessage())","explanation":"EK9 Exception has no getMessage() method. Use reason() to get the exception message. See ek9 -h E50060 for details."},{"error":"E50060","correct":"stdout.println(\"Exit code: \" + $ex.exitCode())","incorrect":"stdout.println(\"Exit code: \" + ex.getExitCode())","explanation":"EK9 Exception uses exitCode(), not getExitCode(). EK9 does not use Java-style getter naming. See ek9 -h E50060 for details."},{"error":"E50060","correct":"stdout.println(`Validation failed on field '${ex.field()}': ${ex.reason()}`)","incorrect":"stdout.println(`Validation failed on field '${ex.getField()}': ${ex.reason()}`)","explanation":"The method is field(), not getField(). EK9 does not use Java-style getter naming conventions. See ek9 -h E50060 for details."},{"error":"E04030","correct":"ex <- Exception(\"Something went wrong\")\n        throw ex","incorrect":"ex <- \"Something went wrong\"\n        throw ex","explanation":"Only Exception types (or subtypes) can be thrown. Throwing a String triggers E04030 — type must be of Exception type. Wrap the message in an Exception constructor. See ek9 -h E04030 for details."},{"error":"E04030","correct":"ex <- Exception(\"Fatal startup error\", 1)\n        throw ex","incorrect":"ex <- \"Fatal startup error\"\n        throw ex","explanation":"Only Exception types can be thrown in EK9. Throwing a String triggers E04030. Create an Exception with the message. See ek9 -h E04030 for details."}],"companions":[]}
{"id":137,"category":"Error Handling and Exceptions","question":"How do I manage resources with try-with-resources in EK9?","url":"https://ek9.io/qa/QA0137.html","alternatePhrasings":["How does try-with-resources work in EK9?","How do I auto-close resources in EK9?","What is the EK9 equivalent of Python with statement?"],"answer":"EK9 supports try-with-resources for automatic resource management. Resources declared in the try header are automatically closed when the scope exits, whether normally or via exception.\n\nRESOURCE BINDING\nDeclare resources in the try header with the arrow syntax:\n  try\n    -> resource <- ResourceClass(\"config\")\n    useResource(resource)\n  catch\n    -> ex as Exception\n    handleError(ex)\nThe resource is created before the try body and automatically closed after.\n\nOPERATOR CLOSE\nA resource class must define 'operator close' to support try-with-resources:\n  ResourceClass\n    name <- String()\n    operator close as pure\n      performCleanup()\nThe close operator is called automatically when the try scope exits.\n\nRESOURCE WITH CATCH\nCombine resources with error handling:\n  try\n    -> conn <- openConnection()\n    data <- conn.read()\n  catch\n    -> ex as Exception\n    handleError(ex)\nThe connection is closed whether read() succeeds or throws.\n\nCOMPARISON WITH OTHER LANGUAGES\nJava: try (var r = new Resource()) { use(r); }\nPython: with open(file) as f: use(f)\nEK9: try -> resource <- Resource() ... (arrow syntax in try header)\nAll three guarantee cleanup. EK9's syntax is consistent with its guard variable pattern.\n\nSee Q134 for basic try/catch. See Q135 for try/catch/finally. See Q78 for guard variables in try blocks.\n\nSee Q333 for transaction try-with-resources pattern.","ek9Example":"defines module qa.errorhandling.trywithresources\n\n  defines class\n\n    <?-\n      A simple resource that tracks open/close state.\n    -?>\n    ManagedResource\n      name <- String()\n\n      ManagedResource()\n        -> name as String\n        this.name: name\n\n      name() as pure\n        <- rtn as String: name\n\n      read() as pure\n        <- rtn as String: \"data from \" + name\n\n      operator close as pure\n        stdout <- Stdout()\n        stdout.println(\"Closing: \" + name)\n\n      override operator ? as pure\n        <- rtn <- true\n\n  defines function\n\n    openResource()\n      -> name as String\n      <- rtn <- ManagedResource(name)\n\n  defines program\n\n    TryWithResourcesDemo()\n      stdout <- Stdout()\n\n      // === BASIC TRY-WITH-RESOURCES ===\n\n      stdout.println(\"=== Basic resource ===\")\n      try\n        -> resource <- ManagedResource(\"config.txt\")\n        content <- resource.read()\n        stdout.println(\"Read: \" + content)\n      catch\n        -> ex as Exception\n        stdout.println(\"Error: \" + $ex)\n\n      // === RESOURCE FROM FUNCTION ===\n\n      stdout.println(\"=== Resource from function ===\")\n      try\n        -> resource <- openResource(\"database\")\n        content <- resource.read()\n        stdout.println(\"Read: \" + content)\n      catch\n        -> ex as Exception\n        stdout.println(\"Error: \" + $ex)\n\n      // === RESOURCE WITH NO EXCEPTION ===\n\n      stdout.println(\"=== Clean exit ===\")\n      result <- String()\n      try\n        -> resource <- ManagedResource(\"temp.dat\")\n        result: resource.read()\n      catch\n        -> ex as Exception\n        result: \"error\"\n\n      stdout.println(\"Result: \" + result)","migrationContext":"Java: try (var r = new Resource()) { ... } with AutoCloseable interface. Python: with open(file) as f: ... with context manager protocol (__enter__/__exit__). Rust: no try-with-resources, RAII with Drop trait handles cleanup automatically. Go: defer file.Close() after opening. Kotlin: use { } extension on Closeable. EK9: try -> resource <- expr with 'operator close' for auto-cleanup, consistent with guard variable syntax.","keywords":["RAII","auto","automatic","catch","cleanup","close","dispose","error","exception","handle","manage","migrate","operator","resource","try"],"primaryTopics":["try with resources","resource management","auto close"],"typicalErrors":[{"error":"E07660","correct":"operator close as pure","incorrect":"close() as pure","explanation":"Try-with-resources requires 'operator close', not a regular method named close. The name 'close' is reserved for the operator and cannot be used as a method name. See ek9 -h E07660 for details."},{"error":"E07620","correct":"operator close as pure","incorrect":"cleanup() as pure","explanation":"Try-with-resources requires the class to define 'operator close'. Without it, the type cannot be used as a resource in a try header. Renaming to a regular method removes the operator. See ek9 -h E07620 for details."},{"error":"E50060","correct":"content <- resource.read()","incorrect":"content <- resource.getData()","explanation":"ManagedResource has a read() method, not getData(). Check the class definition to confirm method names. See ek9 -h E50060 for details."},{"error":"E50060","correct":"stdout.println(\"Read: \" + content)","incorrect":"stdout.println(content.toString())","explanation":"EK9 has no toString() method. Use string concatenation or interpolation. See ek9 -h E50060 for details."}],"companions":[]}
{"id":138,"category":"Error Handling and Exceptions","question":"How do I catch specific exception types in EK9?","url":"https://ek9.io/qa/QA0138.html","alternatePhrasings":["Can I catch different exception types in EK9?","How does exception type matching work in EK9?","How do I handle multiple exception types?"],"answer":"EK9 supports exception type hierarchies. Custom exceptions extend Exception, and catch blocks match by type. You can catch exact types or use polymorphic catching via a base type.\n\nEXCEPTION HIERARCHY\nCreate sibling exception types:\n  NetworkError extends Exception\n    ...\n  TimeoutError extends Exception\n    ...\nBoth extend Exception but are distinct types.\n\nEXACT TYPE MATCHING\nCatch a specific exception type:\n  try\n    throw networkError\n  catch\n    -> ex as NetworkError\n    handleNetworkError(ex)\nOnly NetworkError (and its subtypes) will be caught.\n\nPOLYMORPHIC CATCHING\nCatch the base Exception to handle any exception type:\n  try\n    throw networkError\n  catch\n    -> ex as Exception\n    handleAnyError(ex)\nSince all exceptions extend Exception, catching Exception catches everything.\n\nTYPE MISMATCH BEHAVIOUR\nIf the thrown type does not match the catch type, the exception propagates:\n  try\n    throw networkError\n  catch\n    -> ex as TimeoutError\n    // NOT reached - NetworkError is not TimeoutError\nThe exception passes through to an enclosing try/catch or terminates the program.\n\nNESTED TRY FOR MULTI-TYPE\nUse nested try blocks to handle different exception types:\n  try\n    try\n      riskyOperation()\n    catch\n      -> ex as NetworkError\n      handleNetwork(ex)\n  catch\n    -> ex as Exception\n    handleOther(ex)\nThe inner catch handles NetworkError specifically. Anything else propagates to the outer catch.\n\nSee Q136 for throwing and creating custom exceptions. See Q134 for basic try/catch. See Q102 for the 'as open' modifier used in inheritance.\n\nSee Q308 for the dispatcher pattern for exception type routing.","ek9Example":"defines module qa.errorhandling.exceptionsubtypes\n\n  defines class\n\n    <?-\n      Exception for network-related failures.\n    -?>\n    NetworkError extends Exception\n      host <- String()\n\n      NetworkError()\n        ->\n          reason as String\n          host as String\n\n        super(reason)\n        this.host :=: host\n\n      host() as pure\n        <- rtn as String: host\n\n      default operator ?\n\n    <?-\n      Exception for timeout failures.\n    -?>\n    TimeoutError extends Exception\n      seconds <- Integer()\n\n      TimeoutError()\n        ->\n          reason as String\n          seconds as Integer\n\n        super(reason)\n        this.seconds :=: seconds\n\n      seconds() as pure\n        <- rtn as Integer: seconds\n\n      default operator ?\n\n  defines program\n\n    ExceptionSubtypesDemo()\n      stdout <- Stdout()\n\n      // === EXACT TYPE MATCH ===\n\n      stdout.println(\"=== Exact type match ===\")\n      try\n        ex <- NetworkError(\"Connection refused\", \"api.example.com\")\n        throw ex\n      catch\n        -> ex as NetworkError\n        stdout.println(\"Network error on host: \" + ex.host())\n\n      // === POLYMORPHIC CATCH (base Exception) ===\n\n      stdout.println(\"=== Polymorphic catch ===\")\n      try\n        ex <- NetworkError(\"DNS failure\", \"db.example.com\")\n        throw ex\n      catch\n        -> ex as Exception\n        stdout.println(\"Caught via base Exception: \" + ex.reason())\n\n      // === TYPE MISMATCH (nested try) ===\n\n      stdout.println(\"=== Type mismatch propagation ===\")\n      try\n        try\n          ex <- NetworkError(\"Connection reset\", \"web.example.com\")\n          throw ex\n        catch\n          -> ex as TimeoutError\n          stdout.println(\"Should NOT reach here\")\n      catch\n        -> ex as Exception\n        stdout.println(\"Propagated to outer catch: \" + ex.reason())\n\n      // === NESTED TRY FOR MULTI-TYPE HANDLING ===\n\n      stdout.println(\"=== Multi-type handling ===\")\n      try\n        try\n          ex <- TimeoutError(\"Request timed out\", 30)\n          throw ex\n        catch\n          -> ex as TimeoutError\n          stdout.println(`Timeout after ${ex.seconds()} seconds: ${ex.reason()}`)\n      catch\n        -> ex as Exception\n        stdout.println(\"Other error: \" + ex.reason())","migrationContext":"Java: multiple catch blocks (catch IOException | SQLException), ordered most specific first. Python: multiple except clauses, tuple of types. Rust: no exception types, uses enum variants in Result. Go: errors.Is/errors.As for type checking error chains. Kotlin: multiple catch blocks like Java. EK9: single catch per try block with type matching, use nested try blocks for multiple types, polymorphic matching via base Exception.","keywords":["catch","exception","extends","handle","handler","hierarchy","match","nested","polymorphic","sealed","specific","subtype","type","visitor"],"primaryTopics":[],"typicalErrors":[{"error":"E50060","correct":"ex.host()","incorrect":"ex.getHost()","explanation":"The method is named host(), not getHost(). EK9 does not use Java-style getter naming conventions. Check the class definition to confirm method names. See ek9 -h E50060 for details."},{"error":"E04030","correct":"-> ex as NetworkError\n        stdout.println(\"Network error on host: \" + ex.host())","incorrect":"-> ex as String\n        stdout.println(\"Network error on host: \" + ex)","explanation":"Catch blocks require a type that extends Exception. Catching a String triggers E04030 — type must be of Exception type. See ek9 -h E04030 for details."},{"error":"E50060","correct":"stdout.println(\"Propagated to outer catch: \" + ex.reason())","incorrect":"stdout.println(\"Propagated to outer catch: \" + ex.getMessage())","explanation":"EK9 Exception has no getMessage() method. Use reason() for the error message. See ek9 -h E50060 for details."},{"error":"E50060","correct":"stdout.println(\"Network error on host: \" + ex.host())","incorrect":"stdout.println(\"Network error on host: \" + ex.getHost())","explanation":"The method is host(), not getHost(). EK9 does not use Java-style getter naming. See ek9 -h E50060 for details."},{"error":"E50060","correct":"stdout.println(\"Caught via base Exception: \" + ex.reason())","incorrect":"stdout.println(\"Caught via base Exception: \" + ex.getMessage())","explanation":"EK9 Exception has no getMessage() method. Use reason() to get the exception message. See ek9 -h E50060 for details."}],"companions":[]}
{"id":139,"category":"Error Handling and Exceptions","question":"When should I use try/catch vs Result for error handling?","url":"https://ek9.io/qa/QA0139.html","alternatePhrasings":["Should I use exceptions or Result in EK9?","What is the EK9 error handling philosophy?","When to throw vs return Result?"],"answer":"EK9 provides two error handling approaches: try/catch for truly exceptional situations and Result for expected, recoverable failures. Choosing the right one depends on whether the error is expected or unexpected.\n\nTWO ERROR HANDLING APPROACHES\nTry/catch: For unexpected, exceptional conditions that disrupt normal flow.\nResult: For expected failures that are part of normal operation.\n\nWHEN TO USE TRY/CATCH\nUse try/catch when:\n- The error is truly unexpected (I/O failure, network down)\n- The caller cannot meaningfully recover inline\n- The error represents a broken invariant\n- System-level failures that need stack unwinding\nExample: File system errors, database connection failures, out of memory.\n\nWHEN TO USE RESULT\nUse Result when:\n- Failure is an expected outcome (validation, lookup miss)\n- The caller should handle both paths explicitly\n- You want the compiler to enforce error checking\n- The function signature should communicate that failure is possible\nExample: User input validation, configuration lookup, parsing.\n\nDESIGN GUIDELINES\n1. If the caller must handle the error: use Result\n2. If the error should propagate up: use try/catch\n3. If failure is common (>5% of calls): use Result\n4. If failure is rare and unexpected: use try/catch\n5. For public APIs: prefer Result for clarity\n\nEK9 PHILOSOPHY\nEK9 encourages using Result for most application-level error handling because:\n- The compiler enforces safe access (cannot access .ok() without guard)\n- The function signature is explicit about failure possibility\n- No hidden control flow jumps\nReserve try/catch for truly exceptional situations where Result would be awkward.\n\nSee Q48 for the Result type. See Q134 for basic try/catch. See Q86 for Result guard patterns. See Q87 for Result operations.\n\nSee Q304 for require preconditions. See Q305 for require vs assert vs throw.","ek9Example":"defines module qa.errorhandling.trycatchvsresult\n\n  defines function\n\n    <?-\n      Validates an age value.\n      Returns Result: ok with description, or error code.\n      This is a RESULT approach - failure is expected and common.\n    -?>\n    validateAge()\n      -> age as Integer\n      <- rtn <- Result() of (String, Integer)\n\n      maxAmount <- 150\n      if age < 0\n        rtn: Result(String(), -1)\n      else\n        if age > maxAmount\n          rtn: Result(String(), -2)\n        else\n          rtn: Result(\"Valid: \" + $age, Integer())\n\n    <?-\n      Reads configuration that might fail unexpectedly.\n      Uses TRY/CATCH approach - failure is exceptional.\n    -?>\n    loadConfig()\n      <- rtn <- String()\n\n      // Simulating a config load that succeeds\n      rtn: \"production\"\n\n  defines program\n\n    TryCatchVsResultDemo()\n      stdout <- Stdout()\n\n      // === RESULT APPROACH: Expected failures ===\n\n      stdout.println(\"=== Result approach (validation) ===\")\n\n      ages <- List() of Integer\n      ages += 25\n      ages += -5\n      ages += 200\n\n      for age in ages\n        result <- validateAge(age)\n        if result?\n          stdout.println(\"Valid age: \" + $result.ok())\n        else\n          if result.isError()\n            stdout.println(`Invalid (error code ${result.error()}) for age: ${age}`)\n\n      // === TRY/CATCH APPROACH: Exceptional failures ===\n\n      stdout.println(\"=== Try/catch approach (system errors) ===\")\n\n      try\n        config <- loadConfig()\n        stdout.println(\"Config loaded: \" + config)\n      catch\n        -> ex as Exception\n        stdout.println(\"System error: \" + $ex)\n\n      // === COMBINING BOTH ===\n\n      stdout.println(\"=== Combined approach ===\")\n\n      try\n        ageResult <- validateAge(30)\n        if ageResult?\n          stdout.println(\"Processing age: \" + $ageResult.ok())\n        else\n          if ageResult.isError()\n            stdout.println(\"Bad input: error code \" + $ageResult.error())\n      catch\n        -> ex as Exception\n        stdout.println(\"Unexpected error: \" + $ex)","migrationContext":"Java: exceptions for everything (checked and unchecked), no built-in Result type. Python: exceptions for everything including flow control (StopIteration). Rust: Result for all recoverable errors, panic! for unrecoverable. Go: error return values for everything, no exceptions. Kotlin: exceptions plus kotlin.Result for functional style. EK9: both try/catch AND Result available, philosophy favours Result for expected failures and try/catch for truly exceptional conditions, compiler enforces safe access on both.","keywords":["approach","catch","choose","debug","design","error","exception","guard","handle","handling","ok","philosophy","result","strategy","when"],"primaryTopics":[],"typicalErrors":[{"error":"E08180","correct":"<- rtn <- Result() of (String, Integer)","incorrect":"<- rtn as Result of (String, Integer)","explanation":"The return variable must be initialized. Using '<-' with a constructor ensures the variable starts initialized. Using 'as' without an initializer leaves it uninitialized, triggering E08180 — variable not marked for injection nor initialised. See ek9 -h E08180 for details."},{"error":"E50060","correct":"stdout.println(\"Valid age: \" + $result.ok())","incorrect":"stdout.println(\"Valid age: \" + result.unwrap())","explanation":"EK9 Result has no unwrap() method. Use ok() with a guard check (result?) or isOk(). See ek9 -h E50060 for details."},{"error":"E50060","correct":"stdout.println(\"Config loaded: \" + config)","incorrect":"stdout.println(config.toString())","explanation":"EK9 has no toString() method. Use string concatenation or interpolation instead. See ek9 -h E50060 for details."},{"error":"E50060","correct":"stdout.println(\"System error: \" + $ex)","incorrect":"stdout.println(\"System error: \" + ex.getMessage())","explanation":"EK9 Exception has no getMessage() method. Use reason() for the message or the $ operator for string conversion. See ek9 -h E50060 for details."},{"error":"E50060","correct":"stdout.println(\"Unexpected error: \" + $ex)","incorrect":"stdout.println(\"Unexpected error: \" + ex.getMessage())","explanation":"EK9 Exception has no getMessage() method. Use the $ operator for string conversion. See ek9 -h E50060 for details."}],"companions":[]}
{"id":140,"category":"Constants and Immutability","question":"How do I define constants in EK9?","url":"https://ek9.io/qa/QA0140.html","alternatePhrasings":["How do I declare constants in EK9?","What types can be constants in EK9?","What is the defines constant block?"],"answer":"EK9 provides a dedicated 'defines constant' block at module level for declaring named constant values. Constants use the declaration operator <- with a literal value, and the type is inferred from the literal form.\n\nDEFINES CONSTANT BLOCK\nConstants are declared at module level inside a 'defines constant' block:\n  defines constant\n    maxRetries <- 10\n    greeting <- \"Hello\"\n    Pi <- 3.142\nEach constant uses <- with a literal. No type annotation is needed because EK9 infers the type from the literal syntax.\n\nALL 18 CONSTANT-ELIGIBLE TYPES\nOnly built-in value types can be constants. EK9 supports exactly 18 types:\n  Integer       42, 0xFF, 0b1010\n  Float         3.14, 2.0\n  Bits          0b1010 (bit manipulation)\n  Boolean       true, false\n  Character     'A', 'z'\n  String        \"Hello\"\n  Time          10:30, 23:59:59\n  Date          2024-01-15\n  DateTime      2024-01-15T10:30:00Z\n  Duration      PT2H, PT30M\n  Millisecond   500ms, 1000ms\n  Dimension     2m, 100cm, 5.2kg\n  Resolution    300dpi, 72dpi\n  Colour        #FF0000, #AB6F2B\n  Money         12.50#USD, 100#GBP\n  RegEx         /[a-z]+/, /[S|s]te(?:ven?|phen)/\n  Version       1.0.0-1, 2.3.4-0\n  Path          $?.some.path\nUser-defined classes and records cannot be constants because copy-on-access requires compiler-known copy constructors that only built-in types guarantee.\n\nNATIVE LITERAL SYNTAX\nEK9 provides domain-specific literal syntax for many types. You write Money as 12.50#USD, Colour as #FF0000, Duration as PT2H, Dimension as 2m, and Date as 2024-01-15. These are not strings that get parsed at runtime; they are native literals that the compiler understands directly.\n\nNAMING CONVENTIONS\nEK9 does not enforce a naming convention for constants. Common styles include UPPER_CASE (like MAX_RETRIES) and camelCase (like maxRetries). Choose a convention and apply it consistently across your project.\n\nSee Q23 for the full list of built-in types. See Q31 for Date and Time details. See Q35 for Money. See Q36 for Dimension. See Q141 for how constants are protected via copy-on-access. See Q143 for how EK9 constants compare to const/final/static in other languages. See Q142 for constants across modules. See Q317 for magic literal detection and why constants should be named.","ek9Example":"defines module qa.constants.definition\n\n  defines constant\n    maxRetries <- 10\n    Pi <- 3.142\n    greeting <- \"Hello\"\n    isEnabled <- true\n    initial <- 'A'\n\n    noon <- 12:00\n    releaseDate <- 2024-01-15\n    launchTime <- 2024-01-15T10:30:00Z\n    timeout <- PT30M\n    halfSecond <- 500ms\n\n    pageWidth <- 2m\n    screenDpi <- 300dpi\n    brandColour <- #AB6F2B\n    maxPayment <- 300000#USD\n    matchSteves <- /[S|s]te(?:ven?|phen)/\n    appVersion <- 1.0.0-1\n    configPath <- $?.app.config\n\n  defines program\n\n    DefineConstantsDemo()\n      stdout <- Stdout()\n\n      // Numeric constants\n      stdout.println(`Max retries: ${maxRetries}`)\n      stdout.println(`Pi: ${Pi}`)\n\n      // Text and character\n      stdout.println(`Greeting: ${greeting}`)\n      stdout.println(`Initial: ${initial}`)\n      stdout.println(`Enabled: ${isEnabled}`)\n\n      // Temporal constants\n      stdout.println(`Noon: ${noon}`)\n      stdout.println(`Release: ${releaseDate}`)\n      stdout.println(`Launch: ${launchTime}`)\n      stdout.println(`Timeout: ${timeout}`)\n      stdout.println(`Half second: ${halfSecond}`)\n\n      // Domain constants\n      stdout.println(`Page width: ${pageWidth}`)\n      stdout.println(`Screen DPI: ${screenDpi}`)\n      stdout.println(`Brand colour: ${brandColour}`)\n      stdout.println(`Max payment: ${maxPayment}`)\n      stdout.println(`Version: ${appVersion}`)\n      stdout.println(`Config: ${configPath}`)","migrationContext":"Java: static final fields, no dedicated constant block. Rust: const for compile-time values, limited to scalar-like types. Go: const block for basic types only (no structs/slices/maps). Kotlin: const val for primitives and String only. EK9: defines constant block supporting 18 types including Money, Colour, Dimension with native literal syntax and copy-on-access immutability.","keywords":["constant","define","fixed","immutable","inference","literal","module","type","value"],"primaryTopics":["constant","define constant","const"],"typicalErrors":[{"error":"E50001","correct":"maxRetries <- 10","incorrect":"maxretries <- 10","explanation":"Renaming 'maxRetries' to 'maxretries' breaks all references since EK9 is case-sensitive. The constant name must match exactly wherever it is used. See ek9 -h E50001 for details."},{"error":"E07890","correct":"stdout.println(`Max retries: ${maxRetries}`)","incorrect":"maxRetries := 20","explanation":"Constants declared in a 'defines constant' block are immutable. You cannot reassign maxRetries after declaration. Read the constant or assign it to a mutable variable instead. See ek9 -h E07890 for details."},{"error":"E50001","correct":"stdout.println(`Max retries: ${maxRetries}`)","incorrect":"stdout.println(`Max retries: ${MAX_RETRIES}`)","explanation":"EK9 is case-sensitive. The constant is 'maxRetries' not 'MAX_RETRIES'. EK9 uses camelCase for all identifiers including constants. See ek9 -h E50001 for details."},{"error":"E50060","correct":"stdout.println(`Pi: ${Pi}`)","incorrect":"stdout.println(Pi.toString())","explanation":"EK9 does not have toString(). Use string interpolation or the $ operator. See ek9 -h E50060 for details."}],"companions":[],"oracleToolHint":{"tool":"ek9_scaffold","intent":"constant","description":"Oracle can generate a constants block with typed constant declarations."}}
{"id":141,"category":"Constants and Immutability","question":"How does EK9 protect constants from mutation?","url":"https://ek9.io/qa/QA0141.html","alternatePhrasings":["Can I modify constants in EK9?","Why are constants copied on access?","How does copy-on-access work for constants?"],"answer":"EK9 enforces deep immutability for constants through two mechanisms: copy-on-access and compile-time mutation blocking. Every reference to a constant produces an independent copy, and all mutation operators are blocked at compile time.\n\nCOPY-ON-ACCESS\nEvery time you reference a constant, the compiler generates code that loads the constant value and immediately creates a fresh copy via the type's copy constructor. This means each access gets an independent copy. If you write:\n  msg <- greeting\nThe variable msg receives a copy of the greeting constant, not a reference to the same object.\n\nDEEP IMMUTABILITY\nAll mutation operators are blocked on constants at compile time with error NOT_MUTABLE:\n  ++  --  +=  :=  :=?  :=:  :~:  :^:\nAttempting any of these on a constant produces a compile error. This is not a runtime check; the compiler catches it before any code is generated.\n\nAUTO-CLONING ON PASS\nWhen you pass a constant as a function argument, the compiler auto-clones it. The function receives a copy, so the original constant value is unaffected regardless of what the function does with its parameter.\n\nAUTO-CLONING ON RETURN\nWhen a constant is used in an expression or returned from a function, the result is always a fresh mutable copy. This means you can freely use constants in computations without risk.\n\nVARIABLE COPIES ARE MUTABLE\nOnce you assign a constant to a variable, the variable holds an independent mutable copy:\n  name <- greeting\n  name += \" World\"\nThe variable name is mutable and can be modified. The greeting constant remains unchanged.\n\nWHY ONLY BUILT-IN TYPES\nUser-defined classes and records cannot be constants because copy-on-access requires the compiler to know exactly how to copy the value. Built-in types have guaranteed copy constructors with well-defined semantics. For user-defined types, the compiler cannot guarantee that a copy is truly independent, so it restricts constants to the 18 built-in value types.\n\nSee Q140 for defining constants and the full list of constant-eligible types. See Q130 for mutable vs immutable collection operations. See Q29 for unset/set semantics and how constants are always set. See Q143 for how this compares to const/final/static in other languages. See Q268 for how immutability prevents OWASP data integrity vulnerabilities. See Q317 for magic literal detection that encourages named constants.","ek9Example":"defines module qa.constants.immutability\n\n  defines constant\n    greeting <- \"Hello\"\n    baseCount <- 10\n    price <- 9.99#USD\n\n  defines function\n\n    addSuffix()\n      -> text as String\n      <- rtn as String: text + \" World\"\n\n    doubleIt()\n      -> n as Integer\n      <- rtn as Integer: n + n\n\n  defines program\n\n    ConstantImmutabilityDemo()\n      stdout <- Stdout()\n\n      // === COPY-ON-ACCESS: each reference is a fresh copy ===\n\n      first <- greeting\n      second <- greeting\n      stdout.println(`First: ${first}`)\n      stdout.println(`Second: ${second}`)\n\n      // === VARIABLE COPIES ARE MUTABLE ===\n\n      mutableName <- greeting\n      mutableName += \" World\"\n      stdout.println(`Modified copy: ${mutableName}`)\n\n      // Original constant unchanged on next access\n      stdout.println(`Constant still: ${greeting}`)\n\n      // === CONSTANTS IN EXPRESSIONS ===\n\n      doubled <- baseCount + baseCount\n      stdout.println(`Doubled: ${doubled}`)\n      stdout.println(`Original: ${baseCount}`)\n\n      // === CONSTANTS PASSED TO FUNCTIONS ===\n\n      result <- addSuffix(greeting)\n      stdout.println(`Function result: ${result}`)\n      stdout.println(`Constant after call: ${greeting}`)\n\n      computed <- doubleIt(baseCount)\n      stdout.println(`Computed: ${computed}`)\n      stdout.println(`Base still: ${baseCount}`)\n\n      // === CONSTANTS IN STRING INTERPOLATION ===\n\n      stdout.println(`The price is ${price}`)","migrationContext":"Java: final prevents reassignment but not mutation (final List can still add()). JavaScript: const prevents reassignment but not mutation (const obj = {}; obj.x = 1 works). Rust: const is compile-time only, let bindings are immutable by default. Go: const limited to basic types. EK9: copy-on-access ensures every reference gets a fresh independent copy, plus all mutation operators are blocked at compile time. Truly immutable, no escape hatch.","keywords":["access","clone","constant","copy","deep","immutable","mutation","protection","safety"],"primaryTopics":[],"typicalErrors":[{"error":"E07890","correct":"doubled <- baseCount + baseCount","incorrect":"baseCount += 10","explanation":"Constants are protected by copy-on-access immutability. You cannot use mutating operators like += on a constant. Use the constant in a non-mutating expression to produce a new value instead. See ek9 -h E07890 for details."},{"error":"E07890","correct":"computed <- doubleIt(baseCount)","incorrect":"baseCount := 20","explanation":"Constants cannot be reassigned with :=. Unlike Java final or JavaScript const which only prevent reassignment, EK9 constants block ALL mutation including assignment. Create a new variable from the constant instead. See ek9 -h E07890 for details."},{"error":"E07890","correct":"mutableName <- greeting","incorrect":"greeting++","explanation":"The ++ operator mutates the target variable. Constants cannot be mutated by any operator. Use the constant in a non-mutating expression to compute a new value. See ek9 -h E07890 for details."}],"companions":[]}
{"id":142,"category":"Constants and Immutability","question":"How do I use constants across modules in EK9?","url":"https://ek9.io/qa/QA0142.html","alternatePhrasings":["How do I access constants from another module?","Can I share constants between files in EK9?","How do references work with constants?"],"answer":"EK9 constants belong to the module, not to individual files. Within the same module, constants are visible across all files without any import. For cross-module access, EK9 provides two mechanisms: fully qualified names and the references block.\n\nSAME-MODULE ACCESS\nConstants defined in one file are visible in all other files that define the same module. If two files both declare 'defines module com.example.app', they share the same constant namespace. No import or reference is needed.\n\nFULLY QUALIFIED ACCESS\nYou can access a constant from another module using its fully qualified name:\n  value <- other.module::CONSTANT_NAME\nThe :: operator separates the module name from the constant name. This works anywhere without importing the module first.\n\nREFERENCES IMPORT\nThe 'references' block imports symbols from other modules for short-name access:\n  references\n    net.customer.geometry::Pi\nAfter this declaration, you can use Pi directly instead of net.customer.geometry::Pi. This is purely a convenience; it does not change the constant's behaviour.\n\nMIXING STYLES\nYou can mix short names (from references) and fully qualified names in the same file and even in the same expression. Use whichever is clearer in context. For widely-used constants, import via references. For one-off access, use the fully qualified form.\n\nMODULE-LEVEL SCOPE\nConstants are module-level declarations, just like classes, functions, and programs. They participate in the same visibility and access rules. A constant is accessible from any code that can reference its module.\n\nSee Q6 for how modules work in EK9. See Q13 for the difference between packages and modules. See Q140 for defining constants. See Q141 for copy-on-access protection.","ek9Example":"defines module qa.constants.crossmodule\n\n  defines constant\n    Pi <- 3.14159\n    maxItems <- 100\n    appName <- \"ConstDemo\"\n\n  defines function\n\n    circumference()\n      -> radius as Float\n      <- rtn as Float: 2.0 * Pi * radius\n\n    describe()\n      -> name as String\n      <- rtn as String: `${appName}: ${name}`\n\n  defines program\n\n    CrossModuleConstantsDemo()\n      stdout <- Stdout()\n\n      // === SAME-MODULE ACCESS: constants visible without import ===\n\n      stdout.println(`Pi: ${Pi}`)\n      stdout.println(`Max items: ${maxItems}`)\n      stdout.println(`App: ${appName}`)\n\n      // === CONSTANTS USED IN SAME-MODULE FUNCTIONS ===\n\n      circ <- circumference(5.0)\n      stdout.println(`Circumference of radius 5: ${circ}`)\n\n      desc <- describe(\"test\")\n      stdout.println(`Description: ${desc}`)\n\n      // === CONSTANTS IN EXPRESSIONS ACROSS THE MODULE ===\n\n      halfMax <- maxItems / 2\n      stdout.println(`Half max: ${halfMax}`)\n\n      fullGreeting <- appName + \" v1.0\"\n      stdout.println(`Full: ${fullGreeting}`)","migrationContext":"Java: public static final fields accessed via ClassName.CONSTANT, import statements for short access. Python: module-level variables, import or from-import. Go: exported constants (uppercase) accessible as package.Constant. Rust: pub const in module, use statement for import. EK9: module-level constants, visible across same-module files automatically, fully qualified access via :: or short names via references block.","keywords":["access","constant","cross","import","module","qualified","reference","scope","share"],"primaryTopics":[],"typicalErrors":[{"error":"E07890","correct":"halfMax <- maxItems / 2","incorrect":"maxItems := 50","explanation":"Module-level constants cannot be reassigned. Attempting to modify maxItems with := triggers E07890. Derive new values from constants using expressions instead. See ek9 -h E07890 for details."},{"error":"E50001","correct":"stdout.println(`Pi: ${Pi}`)","incorrect":"stdout.println(`Pi: ${pi}`)","explanation":"EK9 is case-sensitive. The constant is 'Pi' not 'pi'. Constant names must match exactly wherever referenced. See ek9 -h E50001 for details."},{"error":"E50060","correct":"stdout.println(`Max items: ${maxItems}`)","incorrect":"stdout.println(maxItems.toString())","explanation":"EK9 does not have toString(). Use string interpolation or the $ operator. See ek9 -h E50060 for details."}],"companions":[]}
{"id":143,"category":"Constants and Immutability","question":"How do EK9 constants compare to const, final, and static in other languages?","url":"https://ek9.io/qa/QA0143.html","alternatePhrasings":["What is the difference between const and final?","How does EK9 const compare to Java final?","Is EK9 const like Rust const or JavaScript const?"],"answer":"Developers frequently confuse const, final, and static because these keywords overlap differently across languages. Here is how each language handles constants and what EK9 does differently.\n\nTHE CONFUSION\nThe word 'constant' should mean 'this value never changes'. But most languages only guarantee 'this binding cannot be reassigned', which is fundamentally different. A final reference to a mutable object does not make the object constant.\n\nJAVA\nJava uses 'final' to prevent reassignment, but final does NOT prevent mutation:\n  final List<String> names = new ArrayList<>();\n  names.add(\"Alice\"); // This works! The list is mutated.\nThe 'static final' idiom is Java's convention for constants, but even static final objects can be mutated if they are mutable types. Only primitive static final fields are truly constant (inlined by the compiler).\n\nRUST\nRust's 'const' is truly compile-time: the value must be computable at compile time and is limited to scalar-like types. 'static' is runtime-initialized and lives for the program lifetime. 'let' bindings are immutable by default but interior mutability (RefCell, Mutex) can bypass this. Rust comes closest to real immutability but still has escape hatches.\n\nGO\nGo's 'const' is compile-time only and limited to basic types: numbers, strings, and booleans. No structs, slices, or maps. Everything else is a 'var' that you promise not to change, with no compiler enforcement.\n\nKOTLIN\nKotlin's 'const val' is compile-time, limited to primitives and String. 'val' prevents reassignment but not mutation: val list = mutableListOf() still allows list.add(). Similar to Java's final.\n\nJAVASCRIPT\nJavaScript's 'const' is arguably the most misleading: it prevents reassignment but not mutation. const obj = {}; obj.x = 1 works perfectly. The name 'const' suggests immutability but delivers only binding fixity.\n\nEK9\nEK9's 'defines constant' block provides true value immutability through copy-on-access:\n  1. Every access returns a fresh independent copy via copy constructor\n  2. All mutation operators are blocked at compile time (NOT_MUTABLE error)\n  3. Supports 18 types including domain types (Money, Colour, Duration, Dimension)\n  4. No escape hatch: you cannot mutate a constant through any mechanism\nThe key insight is that assigning a constant to a variable gives you a mutable copy. The constant itself is forever protected, but the copy is yours to modify freely.\n\nKEY INSIGHT\nMost languages conflate 'immutable binding' with 'immutable value'. EK9 separates them: the constant value is immutable (copy-on-access), while variables that receive copies are fully mutable. This eliminates the confusion entirely.\n\nSee Q140 for defining constants in EK9. See Q141 for copy-on-access immutability details. See Q130 for mutable vs immutable collection operations.","ek9Example":"defines module qa.constants.comparison\n\n  defines constant\n    greeting <- \"Hello\"\n    count <- 42\n    price <- 9.99#USD\n    colour <- #FF8800\n\n  defines program\n\n    ConstFinalStaticDemo()\n      stdout <- Stdout()\n\n      // === EK9: CONSTANT ACCESS GIVES YOU A COPY ===\n\n      stdout.println(`Constant: ${greeting}`)\n      stdout.println(`Count: ${count}`)\n\n      // === VARIABLE FROM CONSTANT IS MUTABLE ===\n\n      name <- greeting\n      name += \" World\"\n      stdout.println(`Modified copy: ${name}`)\n\n      // Constant unchanged\n      stdout.println(`Constant still: ${greeting}`)\n\n      // === EXPRESSIONS WITH CONSTANTS PRODUCE MUTABLE RESULTS ===\n\n      doubled <- count + count\n      stdout.println(`Doubled: ${doubled}`)\n\n      doubled := doubled + 1\n      stdout.println(`Incremented copy: ${doubled}`)\n\n      // Original constant unchanged\n      stdout.println(`Count still: ${count}`)\n\n      // === DOMAIN TYPE CONSTANTS ===\n\n      stdout.println(`Price: ${price}`)\n      stdout.println(`Colour: ${colour}`)\n\n      localPrice <- price\n      stdout.println(`Local price copy: ${localPrice}`)","migrationContext":"Java: final prevents reassignment not mutation, static final is convention for constants. Rust: const is compile-time only (scalar types), let is immutable binding with interior mutability escape. Go: const limited to basic types, no struct/slice/map. Kotlin: const val for primitives/String only, val prevents reassignment not mutation. JavaScript: const prevents reassignment not mutation (most misleading). EK9: copy-on-access true immutability for 18 types, no escape hatch, variable copies are mutable.","keywords":["comparison","const","constant","final","go","immutable","java","migrate","mutation","reassignment","rust","static"],"primaryTopics":[],"typicalErrors":[{"error":"E07890","correct":"name <- greeting","incorrect":"greeting += \" World\"","explanation":"Unlike Java's final which only prevents reassignment, EK9 constants block ALL mutation operators including +=. Assign the constant to a mutable variable first, then modify the copy. See ek9 -h E07890 for details."},{"error":"E07890","correct":"doubled := doubled + 1","incorrect":"count := count + 1","explanation":"Constants cannot be reassigned. The := operator mutates the target variable, which is forbidden for constants. Copy the constant to a local variable first. See ek9 -h E07890 for details."},{"error":"E07890","correct":"name <- greeting","incorrect":"greeting :=? \"World\"","explanation":"The :=? guarded assignment still mutates the target when it is unset. EK9 constants cannot be mutated through any mechanism including guarded assignment. Constants are always set, so :=? would never trigger anyway. See ek9 -h E07890 for details."}],"companions":[]}
{"id":144,"category":"Control Flow","question":"Why doesn't EK9 have break, continue, or return statements?","url":"https://ek9.io/qa/QA0144.html","alternatePhrasings":["What happened to break and continue in EK9?","Why did EK9 remove the return statement?","Is EK9 missing break, continue, and return, or is that deliberate?","What is the evidence against break, continue, and return?"],"answer":"EK9 deliberately excludes break, continue, and return. This is not a gap or oversight. It is a core design decision backed by decades of production evidence.\n\nTHE DESIGN DECISION\nEK9 removes break, continue, return, and switch fallthrough from the language grammar entirely. These keywords do not exist. The compiler cannot parse them. This eliminates an entire category of bugs at the language level rather than relying on developer discipline.\n\nTHE EVIDENCE\nMicrosoft (2011): 15% of production bugs in C# involved loop control flow errors with break and continue. Apple SSL bug (2014): a duplicated goto statement bypassed SSL certificate validation, affecting millions of devices. Linux Kernel: over 200 CVE fixes traced to break-in-wrong-loop and fallthrough bugs. CERT: switch fallthrough ranked as the 7th most dangerous coding error.\n\nTHE PRINCIPLE\nIf you eliminate a feature from the language, you eliminate every bug that feature can cause. Not reduce. Eliminate. EK9 applies this principle to the four most error-prone control flow mechanisms.\n\nWHAT EK9 PROVIDES INSTEAD\nStream pipelines replace break and continue. Use cat, filter, head, tail, and skip to express what you want rather than how to exit:\n  results <- cat items | filter by isValid | head 5 | collect as List of String\nDeclared return variables replace return statements. The compiler verifies all paths initialise the return:\n  describe() as pure\n    -> number as Integer\n    <- label as String: \"other\"\n    if number > 100\n      label: \"high\"\nMultiple case values replace switch fallthrough:\n  switch day\n    case \"Saturday\", \"Sunday\"\n      type: \"weekend\"\n    default\n      type: \"weekday\"\nGuard expressions replace early returns:\n  if data <- fetchData()\n    process(data)\n\nMODERN TRENDS\nSwift removed fallthrough by default. Rust enforces exhaustive matching. Go removed fallthrough by default. Kotlin sealed classes enforce exhaustive when blocks. EK9 takes these individual improvements to their logical conclusion by removing all four mechanisms.\n\nSee Q50 for declared return variables. See Q69 for multiple case values. See Q89 for stream pipelines. See Q125 for head, tail, and skip. See Q145 for replacing break and continue. See Q146 for function decomposition. See Q147 for switch fallthrough details. See Q148 for migration control flow. See Q268 for how removing these prevents OWASP insecure design vulnerabilities. See Q274 and Q275 for AI hallucination patterns with return, break, and continue. See Q282 through Q289 for practical patterns without break, return, or continue. See Q296 for similar evidence-based naming restrictions. See Q310 for how these removals fit into the quality pyramid.\n\nSee Q338 for how no-return prevents partial transaction commits.","ek9Example":"defines module qa.flow.philosophy.overview\n\n  defines function\n\n    isValid() as pure\n      -> item as String\n      <- valid as Boolean: length item > 0\n\n  defines program\n\n    NoBreakContinueReturnDemo()\n      stdout <- Stdout()\n\n      // === STREAM PIPELINE REPLACES BREAK AND CONTINUE ===\n\n      items <- [\"apple\", \"\", \"banana\", \"\", \"cherry\", \"date\", \"elderberry\"]\n\n      // Filter out empty strings (replaces continue) and take first 3 (replaces break)\n      selected <- cat items | filter by isValid | head 3 | collect as List of String\n      stdout.println(`Selected: ${selected}`)\n\n      // === DECLARED RETURN REPLACES RETURN STATEMENT ===\n\n      label <- describeValue(42)\n      stdout.println(`42 is: ${label}`)\n\n      label2 <- describeValue(150)\n      stdout.println(`150 is: ${label2}`)\n\n      // === MULTIPLE CASE VALUES REPLACE FALLTHROUGH ===\n\n      days <- [\"Monday\", \"Saturday\", \"Wednesday\", \"Sunday\"]\n      for day in days\n        dayType <- String()\n        switch day\n          case \"Monday\", \"Tuesday\", \"Wednesday\", \"Thursday\", \"Friday\"\n            dayType: \"weekday\"\n          case \"Saturday\", \"Sunday\"\n            dayType: \"weekend\"\n          default\n            dayType: \"unknown\"\n        stdout.println(`${day} -> ${dayType}`)\n\n  defines function\n\n    describeValue() as pure\n      -> number as Integer\n      <- label as String: \"normal\"\n      upperLimit <- 100\n      if number > upperLimit\n        label: \"high\"\n      else if number < 0\n        label: \"negative\"","migrationContext":"Java: break and continue in loops, return in methods, switch fallthrough by default (arrow syntax since Java 14 avoids it). Python: break and continue in loops, return in functions, no switch until match/case in 3.10. Rust: break and continue in loops, implicit return via last expression, match is exhaustive with no fallthrough. Go: break and continue in loops, return in functions, switch has no fallthrough by default. C/C++: break and continue, return, switch fallthrough by default (major bug source). Kotlin: break and continue, return, when expression with no fallthrough. Swift: break (rarely needed), continue, return, switch with no fallthrough by default. EK9: none of these exist, replaced by stream pipelines, declared return variables, multiple case values, and guard expressions.","keywords":["apple","branch","break","bug","cert","condition","continue","control","deliberate","design","eliminate","evidence","flow","microsoft","migrate","philosophy","removed","return","safety","ssl"],"primaryTopics":["no break","no continue","no return","why no break"],"typicalErrors":[{"error":"E01070","correct":"selected <- cat items | filter by isValid | head 3 | collect as List of String","incorrect":"selected <- List() of String\n      for item in items\n        if isValid(item)\n          selected += item\n          if length selected >= 3\n            break","explanation":"EK9 has no break statement. Stream pipelines with head replace the loop-with-break pattern. The pipeline expresses intent directly: filter and take 3. See ek9 -h E01070 for details."},{"error":"E01072","correct":"<- label as String: \"normal\"","incorrect":"<- label as String: \"normal\"\n      return label","explanation":"EK9 has no return statement — it was designed out of existence. Declare a return variable with '<-' and the compiler ensures all code paths initialise it. See ek9 -h E01072 for details."},{"error":"E01071","correct":"selected <- cat items | filter by isValid | head 3 | collect as List of String","incorrect":"selected <- cat items | filter by isValid | head 3 | collect as List of String\n      continue","explanation":"EK9 has no continue statement. Stream pipelines with 'filter by' replace skip-and-continue patterns. The pipeline expresses filtering intent directly. See ek9 -h E01071 for details."}],"companions":[]}
{"id":145,"category":"Control Flow","question":"How do I exit a loop early or skip iterations in EK9?","url":"https://ek9.io/qa/QA0145.html","alternatePhrasings":["What replaces break and continue in EK9?","How do I find the first match without break in EK9?","How do I skip items in a loop without continue in EK9?","How do I replace break and continue with stream operations in EK9?"],"answer":"EK9 has no break or continue. Stream pipelines with head, filter, and skip are the designed replacements.\n\nREPLACING BREAK WITH HEAD\nWhere other languages use a loop with break to find the first match, EK9 uses head 1:\n  first <- cat items | filter by isLong | head 1 | collect as List of String\nThe head operation stops the pipeline after N items. No elements beyond that point are processed.\n\nREPLACING CONTINUE WITH FILTER\nWhere other languages skip unwanted items with continue, EK9 uses filter:\n  valid <- cat items | filter by isNonEmpty | collect as List of String\nThe filter operation passes only items that match the predicate. Items that do not match are discarded.\n\nREPLACING COUNTED BREAK WITH HEAD N\nWhere other languages count items and break at a limit, EK9 uses head N:\n  topFive <- cat items | sort | head 5 | collect as List of String\nThis takes exactly 5 items and stops. No counter variable, no break condition.\n\nREPLACING SKIP-AHEAD WITH SKIP\nWhere other languages use continue with a counter to skip initial items, EK9 uses skip:\n  afterHeader <- cat lines | skip 3 | collect as List of String\nThis discards the first 3 elements and passes the rest through.\n\nWHEN LOOPS ARE STILL APPROPRIATE\nFor-in and for-range loops are still the right tool for side effects that process every item:\n  for item in items\n    stdout.println(`Processing: ${item}`)\nLoops work when you need to process ALL items and perform actions. Streams work when you need to select, transform, or limit.\n\nCOMBINING OPERATIONS\nPipeline operations compose naturally:\n  results <- cat items | filter by isValid | skip 2 | head 3 | collect as List of String\nThis filters, skips 2 valid items, then takes the next 3. Each operation has a single responsibility.\n\nSee Q64 for for-range loops. See Q65 for for-in loops. See Q89 for stream pipeline basics. See Q125 for head, tail, and skip details. See Q131 for Python comprehension migration. See Q133 for tee and uniq operations. See Q144 for why break and continue were removed. See Q282 for nested loop patterns. See Q288 for before and after migration examples.","ek9Example":"defines module qa.flow.philosophy.replacingbreak\n\n  defines function\n\n    isLong() as pure\n      -> item as String\n      <- long <- false\n      maxItems <- 5\n      long: length item > maxItems\n\n    isNonEmpty() as pure\n      -> item as String\n      <- nonEmpty as Boolean: length item > 0\n\n    isValid() as pure\n      -> item as String\n      <- valid <- false\n      divisor <- 2\n      valid: length item > divisor\n\n  defines program\n\n    ReplacingBreakContinueDemo()\n      stdout <- Stdout()\n\n      items <- [\"\", \"hi\", \"apple\", \"banana\", \"\", \"cherry\", \"date\", \"elderberry\", \"fig\"]\n\n      // === REPLACING BREAK: FIND FIRST LONG ITEM ===\n\n      firstLong <- cat items | filter by isLong | head 1 | collect as List of String\n      stdout.println(`First long item: ${firstLong}`)\n\n      // === REPLACING CONTINUE: SKIP EMPTY STRINGS ===\n\n      nonEmpty <- cat items | filter by isNonEmpty | collect as List of String\n      stdout.println(`Non-empty: ${nonEmpty}`)\n\n      // === REPLACING COUNTED BREAK: TOP 3 ===\n\n      topThree <- cat items | filter by isNonEmpty | sort | head 3 | collect as List of String\n      stdout.println(`Top 3 sorted: ${topThree}`)\n\n      // === REPLACING SKIP-AHEAD ===\n\n      afterSkip <- cat items | filter by isValid | skip 2 | collect as List of String\n      stdout.println(`After skipping 2 valid: ${afterSkip}`)\n\n      // === COMBINING: FILTER + SKIP + HEAD ===\n\n      window <- cat items | filter by isValid | skip 1 | head 3 | collect as List of String\n      stdout.println(`Window (skip 1, head 3): ${window}`)\n\n      // === LOOP FOR SIDE EFFECTS ===\n\n      stdout.println(\"Processing all valid items:\")\n      for item in items\n        if length item > 0\n          stdout.println(`  -> ${item}`)","migrationContext":"Java: break to exit loop, continue to skip iteration, Streams with limit() and skip() as alternative. Python: break to exit loop, continue to skip, list comprehensions with conditions as alternative. Rust: break to exit loop, continue to skip, iterators with take() and skip() as alternative. Go: break to exit loop, continue to skip, no built-in stream alternative. C/C++: break and continue in loops. Kotlin: break and continue, sequences with take() and drop(). JavaScript: break and continue, array methods filter() and slice(). EK9: no break or continue, stream pipelines with head (replaces break), filter (replaces continue), skip (replaces counted skip), and tail (last N).","keywords":["alternative","branch","break","condition","continue","control","early","exit","filter","first","flow","head","iteration","loop","match","migrate","pipeline","replace","skip","stream"],"primaryTopics":[],"typicalErrors":[{"error":"E01070","correct":"firstLong <- cat items | filter by isLong | head 1 | collect as List of String","incorrect":"firstLong <- String()\n      for item in items\n        if isLong(item)\n          firstLong: item\n          break","explanation":"EK9 has no break statement. Use stream pipelines with head 1 to find the first matching item. The pipeline stops processing after the first match. See ek9 -h E01070 for details."},{"error":"E01071","correct":"nonEmpty <- cat items | filter by isNonEmpty | collect as List of String","incorrect":"nonEmpty <- List() of String\n      for item in items\n        if length item == 0\n          continue\n        nonEmpty += item","explanation":"EK9 has no continue statement. Use stream pipelines with filter to keep only items matching a predicate, replacing the skip-with-continue pattern. See ek9 -h E01071 for details."},{"error":"E01072","correct":"<- long <- false","incorrect":"<- long <- false\n      return true","explanation":"EK9 has no return statement. Declare the return variable with '<-' and assign it conditionally. The compiler ensures all paths initialise the return variable. See ek9 -h E01072 for details."}],"companions":[]}
{"id":146,"category":"Control Flow","question":"How do I structure functions without early return in EK9?","url":"https://ek9.io/qa/QA0146.html","alternatePhrasings":["How do I replace early returns with function decomposition in EK9?","How do I avoid deeply nested if/else in EK9 without return?","What is the decomposition pattern for complex functions in EK9?","How do I write focused single-purpose functions instead of using early returns?"],"answer":"EK9 has no return statement. Instead of using early returns to handle different cases, decompose complex logic into small focused functions that each do one thing.\n\nTHE PROBLEM WITH EARLY RETURNS\nIn other languages, functions grow complex through accumulated early returns: validate input (return if bad), check permissions (return if denied), handle edge cases (return if empty), then finally process. Each return is a hidden exit. Paths multiply. Cleanup can be skipped. Testing requires tracing every possible exit.\n\nTHE EK9 APPROACH: DECOMPOSITION\nInstead of one large function with early returns, write several small functions. Each function does one job. The calling function composes them.\n\nSEPARATE VALIDATION FROM PROCESSING\nWrite a validation function that returns a Boolean or an unset signal:\n  validateAge() as pure\n    -> age as Integer\n    <- valid as Boolean: age >= 0 and age <= 150\nThen guard on the result:\n  if validateAge(age)\n    processAge(age)\n\nSEPARATE DIFFERENT PROCESSING PATHS\nInstead of if/else chains with early returns, use a categorisation function followed by a switch:\n  categorise() as pure\n    -> score as Integer\n    <- category as String: \"average\"\n    if score >= 90\n      category: \"excellent\"\n    else if score >= 70\n      category: \"good\"\n    else if score < 40\n      category: \"poor\"\n\nWHY DECOMPOSITION IS BETTER\nEach function does one job. All paths through a function are visible at a glance. There are no hidden exits. Each function is independently testable. The calling code reads as a sequence of clear steps rather than a maze of guard clauses.\n\nGUARDS FOR CONDITIONAL EXECUTION\nUse guard variables to execute code only when a value is available:\n  if record <- findRecord(id)\n    process(record)\nThe body only executes if findRecord returns a set value. This replaces the early-return-if-null pattern.\n\nUNSET RETURN AS SIGNAL\nFunctions can return an unset value to signal that no result was found:\n  findByName()\n    ->\n      name as String\n      items as List of String\n    <- found as String: String()\n    for item in items\n      if item == name\n        found: item\nThe caller uses a guard to check: if result <- findByName(name, items).\n\nSee Q50 for declared return variables. See Q51 for abstract function patterns. See Q56 for higher-order function composition. See Q74 for guard variables in if statements. See Q144 for why return was removed. See Q145 for replacing break and continue. See Q285 for multiple precondition patterns. See Q289 for before and after migration examples.","ek9Example":"defines module qa.flow.philosophy.decomposition\n\n  defines function\n\n    // Validation function: one job\n    validateAge() as pure\n      -> age as Integer\n      <- valid <- false\n      maxAge <- 150\n      valid: age >= 0 and age <= maxAge\n\n    // Categorisation function: one job\n    categorise() as pure\n      -> score as Integer\n      <- category as String: \"average\"\n      excellentMin <- 90\n      goodMin <- 70\n      poorMax <- 40\n      if score >= excellentMin\n        category: \"excellent\"\n      else if score >= goodMin\n        category: \"good\"\n      else if score < poorMax\n        category: \"poor\"\n\n    // Description function: one job\n    describeCategory() as pure\n      -> category as String\n      <- description as String: category + \" performance\"\n\n    // Search function: returns unset if not found\n    findItem()\n      ->\n        name as String\n        items as List of String\n      <- found as String: String()\n      for item in items\n        if item == name\n          found: item\n\n  defines program\n\n    DecompositionDemo()\n      stdout <- Stdout()\n\n      // === VALIDATION THEN PROCESSING ===\n\n      ages <- [25, -5, 200, 42, 0, 150]\n      for age in ages\n        if validateAge(age)\n          stdout.println(`Valid age: ${age}`)\n        else\n          stdout.println(`Invalid age: ${age}`)\n\n      // === CATEGORISE THEN DESCRIBE ===\n\n      scores <- [95, 72, 55, 38, 85]\n      for score in scores\n        category <- categorise(score)\n        description <- describeCategory(category)\n        stdout.println(`Score ${score}: ${description}`)\n\n      // === GUARD ON SEARCH RESULT ===\n\n      fruits <- [\"apple\", \"banana\", \"cherry\"]\n\n      if result <- findItem(\"banana\", fruits)\n        stdout.println(`Found: ${result}`)\n\n      if result <- findItem(\"mango\", fruits)\n        stdout.println(`Found: ${result}`)\n      else\n        stdout.println(\"Mango not found\")","migrationContext":"Java: early return for validation, guard clauses common, Extract Method refactoring in IDE. Python: early return common, guard clauses with if/return at top of function. Rust: early return with ?, pattern matching replaces some guard clauses. Go: early return for error checking (if err != nil return), very common pattern. Kotlin: early return, when expression reduces some nesting. Swift: guard let for early return on nil. EK9: no return statement, decompose into focused functions, guard expressions for conditional execution, unset return as signal, compiler verifies all paths initialise return variable.","keywords":["branch","compose","condition","control","decomposition","early","exit","flow","focused","function","guard","isset","nested","null-safe","path","process","refactor","return","safe","single","validate"],"primaryTopics":[],"typicalErrors":[{"error":"E01072","correct":"validateAge() as pure\n      -> age as Integer\n      <- valid <- false\n      maxAge <- 150\n      valid: age >= 0 and age <= maxAge","incorrect":"validateAge() as pure\n      -> age as Integer\n      if age < 0 or age > 150\n        return false\n      return true","explanation":"EK9 has no return statement. Functions use declared return variables. Decompose complex logic into small focused functions that each assign to their return variable. See ek9 -h E01072 for details."},{"error":"E50060","correct":"category <- categorise(score)","incorrect":"category <- categorise(score).toUpperCase()","explanation":"String has no toUpperCase() method. Use upperCase() instead. See ek9 -h E50060 for details."},{"error":"E01073","correct":"<- found as String: String()","incorrect":"<- found as String: null","explanation":"EK9 has no null. Use an unset value (String()) to signal 'not found'. The caller uses a guard expression: if result <- findItem(name). See ek9 -h E01073 for details."}],"companions":[]}
{"id":147,"category":"Control Flow","question":"Why doesn't EK9 have switch fallthrough and how do I group cases?","url":"https://ek9.io/qa/QA0147.html","alternatePhrasings":["How does EK9 prevent switch fallthrough bugs?","What is wrong with switch fallthrough in other languages?","How do I handle multiple values in one case without fallthrough in EK9?","How do comma-separated case values replace switch fallthrough in EK9?"],"answer":"Switch fallthrough is one of the most dangerous features in programming. EK9 eliminates it entirely and provides a safer, clearer alternative.\n\nTHE FALLTHROUGH PROBLEM\nIn C, Java, and JavaScript, switch cases fall through to the next case by default. Forgetting a break statement causes silent execution of unintended code. CERT ranks this as the 7th most dangerous coding error. The bug is insidious because the code compiles and often appears to work until an edge case triggers the missing break.\n\nA CLASSIC FALLTHROUGH BUG\nIn Java, this code silently assigns the wrong category:\n  switch (code) {\n    case 1: result = \"admin\";\n    // missing break - falls through!\n    case 2: result = \"user\";\n    break;\n  }\nWhen code is 1, result becomes \"user\" instead of \"admin\". The compiler gives no warning. This pattern has caused countless production bugs.\n\nEK9 SOLUTION: MULTIPLE VALUES PER CASE\nEK9 uses comma-separated values in a single case clause:\n  switch code\n    case 1, 2, 3\n      category: \"low\"\n    case 4, 5\n      category: \"high\"\n    default\n      category: \"unknown\"\nEach case runs its body and stops. There is no fallthrough mechanism in the language.\n\nSWITCH AS EXPRESSION\nSwitch can return a value directly:\n  label <- switch priority\n    <- rtn as String?\n    case 1, 2\n      rtn: \"Low\"\n    case 3\n      rtn: \"Medium\"\n    case 4, 5\n      rtn: \"High\"\n    default\n      rtn: \"Unknown\"\n\nCOMPARISON OPERATORS IN CASES\nCases can use comparison operators for range matching:\n  switch score\n    case >= 90\n      grade: \"A\"\n    case >= 80\n      grade: \"B\"\n    case >= 70\n      grade: \"C\"\n    default\n      grade: \"F\"\n\nENUM EXHAUSTIVENESS\nWith enumerations, the compiler can verify all values are handled:\n  switch colour\n    case Colour.Red\n      label: \"stop\"\n    case Colour.Amber\n      label: \"caution\"\n    case Colour.Green\n      label: \"go\"\n\nSee Q63 for basic switch syntax. See Q68 for switch as expression. See Q69 for multiple case values. See Q70 for comparison operators in cases. See Q73 for enum exhaustiveness. See Q144 for the full control flow philosophy.","ek9Example":"defines module qa.flow.philosophy.nofallthrough\n\n  defines type\n    Colour\n      Red\n      Amber\n      Green\n\n  defines program\n\n    NoFallthroughDemo()\n      stdout <- Stdout()\n\n      // === MULTIPLE VALUES PER CASE ===\n\n      codes <- [1, 2, 3, 4, 5, 6]\n      for code in codes\n        category <- String()\n        switch code\n          case 1, 2, 3\n            category: \"low\"\n          case 4, 5\n            category: \"high\"\n          default\n            category: \"unknown\"\n        stdout.println(`Code ${code} -> ${category}`)\n\n      // === SWITCH AS EXPRESSION ===\n\n      priorities <- [1, 3, 5]\n      for priority in priorities\n        label <- switch priority\n          <- rtn as String?\n          case 1, 2\n            rtn: \"Low\"\n          case 3\n            rtn: \"Medium\"\n          case 4, 5\n            rtn: \"High\"\n          default\n            rtn: \"Unknown\"\n        stdout.println(`Priority ${priority}: ${label}`)\n\n      // === COMPARISON OPERATORS IN CASES ===\n\n      scores <- [95, 82, 71, 55]\n      for score in scores\n        grade <- String()\n        switch score\n          case >= 90\n            grade: \"A\"\n          case >= 80\n            grade: \"B\"\n          case >= 70\n            grade: \"C\"\n          default\n            grade: \"F\"\n        stdout.println(`Score ${score}: grade ${grade}`)\n\n      // === ENUM SWITCH ===\n\n      colours <- [Colour.Red, Colour.Amber, Colour.Green]\n      for colour in colours\n        meaning <- String()\n        switch colour\n          case Colour.Red\n            meaning: \"stop\"\n          case Colour.Amber\n            meaning: \"caution\"\n          case Colour.Green\n            meaning: \"go\"\n          default\n            meaning: \"unknown\"\n        stdout.println(`${colour} means ${meaning}`)","migrationContext":"Java: switch fallthrough by default, break required to prevent it, switch expressions with arrow syntax since Java 14 eliminate fallthrough. C/C++: switch fallthrough by default, break required, Clang and GCC warn but cannot prevent. JavaScript: switch fallthrough by default, break required. Python: match/case since 3.10 with no fallthrough, pipe operator for multiple values. Rust: match with no fallthrough, pipe operator for multiple patterns, exhaustive. Go: switch with no fallthrough by default (opposite of C), explicit fallthrough keyword if needed. Kotlin: when with no fallthrough, comma-separated values. Swift: switch with no fallthrough, comma-separated values, exhaustive. C#: switch with no fallthrough between non-empty cases. EK9: no fallthrough mechanism exists, comma-separated case values, comparison operators in cases, switch as expression, enum exhaustiveness.","keywords":["branch","break","bug","case","cert","comma","condition","control","danger","exhaustive","expression","fallthrough","flow","migrate","multiple","safety","silent","switch","values"],"primaryTopics":[],"typicalErrors":[{"error":"E01070","correct":"case 1, 2, 3\n            category: \"low\"","incorrect":"case 1, 2, 3\n            category: \"low\"\n            break","explanation":"EK9 has no break statement. Each case clause is self-contained with no fallthrough. The break keyword does not exist in the language. See ek9 -h E01070 for details."},{"error":"E01072","correct":"stdout.println(`Priority ${priority}: ${label}`)","incorrect":"return label","explanation":"EK9 has no return statement. Switch expressions automatically return the declared variable's value. There is no need (and no way) to explicitly return from a program. See ek9 -h E01072 for details."},{"error":"E07320","correct":"default\n            category: \"unknown\"","incorrect":"//no default case","explanation":"Non-enum switch statements require a default clause to handle unexpected values. Without it, the compiler reports E07320. This ensures every possible value is handled. Enum switches with all values covered do not need default. See ek9 -h E07320 for details."}],"companions":[]}
{"id":148,"category":"Control Flow","question":"How do I migrate complex control flow from Java or Python to EK9?","url":"https://ek9.io/qa/QA0148.html","alternatePhrasings":["How do I rewrite a Java loop with break and continue in EK9?","How do I translate early return patterns to EK9?","What is the step-by-step process for converting control flow to EK9?","What are the migration rules for converting break, continue, and return to EK9?"],"answer":"Five rules map familiar control flow patterns to EK9 equivalents.\n\nRULE 1: BREAK BECOMES HEAD\n  results <- cat items | head 3 | collect as List of String\n\nRULE 2: CONTINUE BECOMES FILTER\n  valid <- cat items | filter by isNonEmpty | collect as List of String\n\nRULE 3: EARLY RETURN BECOMES IF GUARD\n  if data <- fetch()\n    process(data)\n\nRULE 4: MULTIPLE RETURNS BECOME DECOMPOSITION\n  category <- categorise(amount)\n  label <- describe(category)\n\nRULE 5: FALLTHROUGH BECOMES COMMA-SEPARATED CASES\n  switch code\n    case 1, 2, 3\n      result: \"low\"\n\nTHE MENTAL MODEL SHIFT\nThink 'what do I want?' not 'how do I exit?'. cat items | filter by isValid | head 3 | collect expresses intent directly.\n\nSee Q50 for return variables. See Q89 for stream pipelines. See Q125 for head/tail/skip. See Q131 for Python migration. See Q132 for Java migration. See Q144 for control flow philosophy. See Q145 for break/continue replacements. See Q146 for decomposition. See Q147 for fallthrough replacements. See Q288 for break loop migration. See Q289 for return migration. See Q748 for Swift migration.","ek9Example":"defines module qa.flow.philosophy.migration\n\n  defines function\n\n    isNonEmpty() as pure\n      -> item as String\n      <- nonEmpty as Boolean: length item > 0\n\n    isExpensive() as pure\n      -> item as String\n      <- expensive <- false\n      minExpensiveLength <- 6\n      expensive: length item > minExpensiveLength\n\n    categorise() as pure\n      -> amount as Integer\n      <- category as String: \"medium\"\n      highThreshold <- 100\n      lowThreshold <- 10\n      if amount >= highThreshold\n        category: \"high\"\n      else if amount < lowThreshold\n        category: \"low\"\n\n    describe() as pure\n      -> category as String\n      <- label as String: \"standard item\"\n      switch category\n        case \"high\"\n          label: \"premium item\"\n        case \"low\"\n          label: \"budget item\"\n        default\n          label: \"standard item\"\n\n    findByName()\n      ->\n        target as String\n        items as List of String\n      <- found as String: String()\n      for item in items\n        if item == target\n          found: item\n\n  defines program\n\n    MigrationDemo()\n      stdout <- Stdout()\n\n      items <- [\"\", \"apple\", \"banana\", \"\", \"cherry\", \"date\", \"elderberry\", \"fig\", \"grape\"]\n\n      // === RULE 1: BREAK -> HEAD ===\n\n      firstThree <- cat items | filter by isNonEmpty | head 3 | collect as List of String\n      stdout.println(`First 3 non-empty: ${firstThree}`)\n\n      // === RULE 2: CONTINUE -> FILTER ===\n\n      nonEmpty <- cat items | filter by isNonEmpty | collect as List of String\n      stdout.println(`Non-empty items: ${nonEmpty}`)\n\n      // === RULE 3: EARLY RETURN -> GUARD ===\n\n      names <- [\"Alice\", \"Bob\", \"Charlie\"]\n\n      if found <- findByName(\"Bob\", names)\n        stdout.println(`Found: ${found}`)\n\n      if found <- findByName(\"Dave\", names)\n        stdout.println(`Found: ${found}`)\n      else\n        stdout.println(\"Dave not found\")\n\n      // === RULE 4: MULTIPLE RETURNS -> DECOMPOSITION ===\n\n      amounts <- [5, 50, 150]\n      for amount in amounts\n        category <- categorise(amount)\n        label <- describe(category)\n        stdout.println(`Amount ${amount}: ${label}`)\n\n      // === RULE 5: FALLTHROUGH -> COMMA CASES ===\n\n      codes <- [1, 2, 3, 4, 5]\n      for code in codes\n        level <- String()\n        switch code\n          case 1, 2, 3\n            level: \"basic\"\n          case 4, 5\n            level: \"advanced\"\n          default\n            level: \"unknown\"\n        stdout.println(`Code ${code}: ${level}`)\n\n      // === COMBINED: THE MENTAL MODEL SHIFT ===\n      // \"I want the first 2 expensive items\" (not \"loop, check, count, break\")\n\n      expensive <- cat items | filter by isExpensive | head 2 | collect as List of String\n      stdout.println(`First 2 expensive: ${expensive}`)","migrationContext":"Java/Python/Rust/Go/JS: break, continue, return, switch fallthrough. EK9: five rules map to stream pipelines, guard expressions, function decomposition, comma-separated cases.","keywords":["branch","break","condition","continue","control","convert","flow","java","mental","migrate","migration","model","pattern","python","return","rewrite","rule","step","swift","translate"],"primaryTopics":[],"typicalErrors":[{"error":"E01070","correct":"firstThree <- cat items | filter by isNonEmpty | head 3 | collect as List of String","incorrect":"firstThree <- List() of String\n      for item in items\n        if isNonEmpty(item)\n          firstThree += item\n          if length firstThree >= 3\n            break","explanation":"EK9 has no break statement. Migration Rule 1: break becomes head. Use stream pipelines with head N instead of loop-with-break-at-count. See ek9 -h E01070 for details."},{"error":"E01071","correct":"nonEmpty <- cat items | filter by isNonEmpty | collect as List of String","incorrect":"nonEmpty <- List() of String\n      for item in items\n        if not isNonEmpty(item)\n          continue\n        nonEmpty += item","explanation":"EK9 has no continue statement. Migration Rule 2: continue becomes filter. Use stream pipelines with filter to keep only matching items. See ek9 -h E01071 for details."},{"error":"E01072","correct":"<- found as String: String()","incorrect":"<- found as String: String()\n      return found","explanation":"EK9 has no return statement. Declare the return variable with a default value and conditionally assign. The compiler ensures all paths initialize the return variable. See ek9 -h E01072 for details."},{"error":"E01075","correct":"items <- [\"\", \"apple\", \"banana\", \"\", \"cherry\", \"date\", \"elderberry\", \"fig\", \"grape\"]","incorrect":"items <- new List(\"\", \"apple\", \"banana\", \"\", \"cherry\", \"date\", \"elderberry\", \"fig\", \"grape\")","explanation":"EK9 has no 'new' keyword. Types are instantiated by calling the constructor directly. Java's 'new ArrayList<>()' becomes List() of Type in EK9. See ek9 -h E01075 for details."}],"companions":[]}
{"id":149,"category":"Getting Started","question":"Why does EK9 have a built-in Money type and how does it handle currency safety?","url":"https://ek9.io/qa/QA0149.html","alternatePhrasings":["Why not use BigDecimal or a money library?","What happens when I add GBP to USD in EK9?","How does EK9 prevent money bugs that plague other languages?","Why is Money built into EK9 instead of being a library?"],"answer":"EK9 makes Money a built-in type because money is almost always implemented incorrectly in other languages. This is not a theoretical concern — it is the norm across the industry.\n\nCOMMON MONEY BUGS IN OTHER LANGUAGES\n  - Using float or double: 0.1 + 0.2 != 0.3 in IEEE 754\n  - Using BigDecimal but forgetting the rounding mode on one operation out of hundreds\n  - Using BigDecimal with the double constructor: new BigDecimal(0.1) produces 0.100000000000000005551... not 0.1\n  - Hardcoding 2 decimal places everywhere, then failing for JPY (0 decimals), BHD (3 decimals), or CLF (4 decimals)\n  - Storing currency code separately from amount, then mixing them up during refactoring\n  - Silently adding GBP to USD and getting a plausible but completely wrong number\nThese bugs are among the most expensive in software because they go undetected for months — the numbers look close enough until an audit finds millions in discrepancies.\n\nMIXED CURRENCY RETURNS UNSET\nAdding GBP to USD does not throw an exception. It returns an unset Money value:\n  mixed <- 10#GBP + 30#USD    mixed is unset (mixed? is false)\n  compare <- 10#GBP == 30#USD  compare is unset (not true, not false)\nThis follows the EK9 tri-state pattern. The compiler cannot catch this at compile time because variables could hold any currency at runtime. Instead, mixed currency operations return unset — forcing you to check before using the result.\n\nDIVISION BY ZERO RETURNS UNSET\n  bad <- 100#GBP / 0    bad is unset (bad? is false)\nOperations that cannot produce a meaningful result return unset rather than crashing.\n\nWHAT EK9 ELIMINATES\nThe literal 10#GBP encodes amount, currency, and precision in a single expression. The compiler knows all ISO 4217 currencies and their correct decimal places. Rounding is automatic and consistent (HALF_UP). Mixed currency operations return unset instead of silently producing wrong answers. There is nothing to forget, nothing to configure, and no way to accidentally use the wrong precision.\n\nSee Q35 for Money arithmetic and literals. See Q150 for currency conversion and locale formatting. See Q29 for the tri-state (absent/unset/set) model.","ek9Example":"defines module qa.money.safety\n\n  defines program\n    MoneySafetyDemo()\n      stdout <- Stdout()\n\n      tenPounds <- 10#GBP\n      thirtyDollars <- 30.20#USD\n\n      // === MIXED CURRENCY RETURNS UNSET ===\n\n      // Cannot accidentally add GBP to USD — returns unset, not an exception\n      mixedResult <- tenPounds + thirtyDollars\n      require ~mixedResult?\n      stdout.println(`Mixed currency isSet: ${mixedResult?}`)\n\n      // Comparison across currencies also returns unset\n      mixedCompare <- tenPounds == thirtyDollars\n      require ~mixedCompare?\n      stdout.println(`Mixed compare isSet: ${mixedCompare?}`)\n\n      // === DIVISION BY ZERO RETURNS UNSET ===\n\n      badResult <- tenPounds / 0\n      require ~badResult?\n      stdout.println(`Div by zero isSet: ${badResult?}`)\n\n      // === SAME CURRENCY WORKS NORMALLY ===\n\n      total <- tenPounds + 89.51#GBP\n      require total?\n      require total == 99.51#GBP\n      stdout.println(`Same currency total: ${total}`)\n\n      // === GUARD PATTERN FOR SAFE MONEY OPERATIONS ===\n\n      // Use guard to safely handle potentially unset results\n      if safeTotal <- tenPounds + 5#GBP\n        stdout.println(`Safe total: ${safeTotal}`)\n\n      // Mixed currency — guard body does NOT execute\n      if unsafeTotal <- tenPounds + thirtyDollars\n        stdout.println(\"This never prints\")\n      else\n        stdout.println(\"Mixed currency detected via guard\")","migrationContext":"Java: BigDecimal with explicit RoundingMode at every operation, Currency class separate from amount, mixed currency not detected. Python: decimal.Decimal requires context for rounding, no currency awareness, money libraries (py-moneyed) needed. JavaScript: IEEE 754 floating-point causes 0.1+0.2!=0.3 bugs, Dinero.js or similar needed. Ruby: no built-in, money gem needed. Go: no built-in, shopspring/decimal or similar. Rust: no built-in, rust_decimal crate. C#: decimal type has precision but no currency. EK9: built-in Money type eliminates every one of these mistakes at the language level.","keywords":["beginner","bigdecimal","bug","built-in","currency","financial","first","float","intro","iso4217","migrate","mixed","money","precision","rounding","safety","start","unset"],"primaryTopics":[],"typicalErrors":[{"error":"E50060","correct":"tenPounds + thirtyDollars","incorrect":"tenPounds.getCurrency()","explanation":"EK9 has no getCurrency() method on Money. Use the #> (extract-right) prefix operator to get the currency code as a String. AI models from Java and Python generate getter methods that do not exist in EK9. See ek9 -h E50060 for details."},{"error":"E50060","correct":"tenPounds + 89.51#GBP","incorrect":"tenPounds.add(89.51#GBP)","explanation":"EK9 Money uses operators for arithmetic, not method calls. There is no add() method. Use + for addition, - for subtraction. AI models transfer Java BigDecimal patterns that do not exist in EK9. See ek9 -h E50060 for details."},{"error":"E50060","correct":"tenPounds / 0","incorrect":"tenPounds.getAmount()","explanation":"EK9 has no getAmount() method on Money. Use the #< (extract-left) prefix operator to extract the numeric amount as a Float. AI models transfer Java BigDecimal getter patterns that do not exist in EK9. See ek9 -h E50060 for details."},{"error":"E50060","correct":"tenPounds + 5#GBP","incorrect":"tenPounds.setScale(2)","explanation":"EK9 Money handles rounding automatically using HALF_UP and the currency's ISO 4217 decimal places. There is no setScale() or round() method. GBP uses 2 decimals, JPY uses 0, CLF uses 4 — all handled automatically. See ek9 -h E50060 for details."}],"companions":[]}
{"id":150,"category":"Getting Started","question":"How do I convert currencies and format money for display in EK9?","url":"https://ek9.io/qa/QA0150.html","alternatePhrasings":["How do I convert between currencies in EK9?","How do I format money with locale in EK9?","How do I sum a list of money values in EK9?","How does locale affect money display in EK9?"],"answer":"EK9 provides currency conversion, locale-aware formatting, and stream collection for Money values.\n\nCURRENCY CONVERSION\nTwo overloads for converting between currencies:\n  inUSD <- amount.convert(1.32845, \"USD\")    Rate + target currency string\n  inUSD <- amount.convert(1.32845#USD)        Rate embedded in money literal\nBoth multiply the amount by the rate and return a new Money value in the target currency, rounded to that currency's precision.\n\nLOCALE FORMATTING\nFour levels of locale-aware formatting using the Locale type:\n  locale.format(money)         Full: currency symbol + amount + fractions\n  locale.longFormat(money)     No symbol: amount + fractions\n  locale.mediumFormat(money)   No fractions: currency symbol + amount\n  locale.shortFormat(money)    Neither: amount only\nThe same money value formats differently in different locales:\n  en_GB: GBP 10.00    de_DE: 10,00 GBP\n\nSTREAM COLLECTION\nCollect a stream of money values into a total:\n  amounts as List of Money := [10#GBP, 89.51#GBP, 49.76#GBP]\n  total <- cat amounts | collect as Money\nThe collect operation adds all values in the stream. All values must be the same currency — mixed currencies produce an unset result.\n\nSee Q35 for Money arithmetic and literals. See Q149 for currency safety and why Money is built-in. See Q44 for the complete Locale type API. See Q89 for stream pipeline basics.","ek9Example":"defines module qa.money.formatting\n\n  defines program\n    MoneyFormattingDemo()\n      stdout <- Stdout()\n\n      tenPounds <- 10#GBP\n      amounts as List of Money := [tenPounds, 89.51#GBP, 49.76#GBP]\n\n      // === STREAM COLLECTION ===\n\n      streamTotal <- cat amounts | collect as Money\n      require streamTotal == 159.27#GBP\n      stdout.println(`Stream total: ${streamTotal}`)\n\n      // === CURRENCY CONVERSION ===\n\n      // Convert with exchange rate and target currency\n      totalInUSD <- streamTotal.convert(1.32845, \"USD\")\n      require totalInUSD == 211.58#USD\n      stdout.println(`Converted: ${totalInUSD}`)\n\n      // Alternative syntax: convert with Money literal\n      alsoUSD <- streamTotal.convert(1.32845#USD)\n      require alsoUSD == 211.58#USD\n\n      // === LOCALE FORMATTING ===\n\n      enGB <- Locale(\"en_GB\")\n      deutsch <- Locale(\"de_DE\")\n\n      // Four format levels\n      stdout.println(`Full: ${enGB.format(tenPounds)}`)\n      stdout.println(`No symbol: ${enGB.longFormat(tenPounds)}`)\n      stdout.println(`No fractions: ${enGB.mediumFormat(tenPounds)}`)\n      stdout.println(`Neither: ${enGB.shortFormat(tenPounds)}`)\n\n      // Same currency, different locale\n      stdout.println(`German: ${deutsch.format(tenPounds)}`)","migrationContext":"Java: BigDecimal with explicit RoundingMode, NumberFormat for locale, Currency class separate from amount. Python: decimal.Decimal + locale module, babel for formatting. JavaScript: Intl.NumberFormat for locale, no built-in currency conversion. Rust: no built-in, external crates for both. Go: no built-in, golang.org/x/text for formatting. EK9: built-in convert() method with two overloads, four locale formatting levels, stream collection with cat|collect as Money.","keywords":["beginner","collect","conversion","convert","currency","display","exchange","first","format","formatting","intro","locale","money","rate","start","stream"],"primaryTopics":[],"typicalErrors":[{"error":"E50060","correct":"stdout.println(`Full: ${enGB.format(tenPounds)}`)","incorrect":"stdout.println(`Full: ${tenPounds.format(enGB)}`)","explanation":"Formatting is done via the Locale type, not a method on Money. Create a Locale then call locale.format(money). Four levels available: format, longFormat, mediumFormat, shortFormat. See ek9 -h E50060 for details."},{"error":"E50060","correct":"stdout.println(`Stream total: ${streamTotal}`)","incorrect":"stdout.println(streamTotal.toString())","explanation":"EK9 has no toString() method. Use string interpolation or the $ prefix operator for string conversion. See ek9 -h E50060 for details."},{"error":"E50060","correct":"totalInUSD <- streamTotal.convert(1.32845, \"USD\")","incorrect":"totalInUSD <- streamTotal.convert(\"USD\")","explanation":"Currency conversion requires an exchange rate. The convert method takes either (Float, String) or (Money) — the rate is always required. There is no automatic rate lookup. See ek9 -h E50060 for details."}],"companions":[]}
{"id":151,"category":"File I/O","question":"How do I read a text file in EK9?","url":"https://ek9.io/qa/QA0151.html","alternatePhrasings":["How do I open and read a file in EK9?","What is TextFile in EK9?","How do I load file contents in EK9?"],"answer":"EK9 provides the TextFile type for reading and writing text files. Create a TextFile with a path string, then use try-with-resources to open an input stream and read the contents safely.\n\nCREATING A TEXTFILE\nCreate a TextFile by passing a file path as a String:\n  file <- TextFile(\"data.txt\")\nThe file is not opened until you call input() or output(). The TextFile just records the path.\n\nREADING WITH INPUT STREAM\nUse file.input() to get a CloseableStringInput stream:\n  try\n    -> input <- file.input()\n    cat input > stdout\nThe input stream provides lines from the file one at a time. You can pipe them through stream operations or consume them directly.\n\nTRY WITH RESOURCES\nAlways open file streams inside a try block. EK9's try-with-resources automatically closes the stream when the block exits, whether normally or via exception. This prevents resource leaks.\n\nHANDLING FILE NOT FOUND\nIf the file does not exist, file.input() returns an unset value. The try guard (try -> input <- file.input()) only enters the body if input is set, so you naturally handle missing files without explicit null checks.\n\nSee Q3 for printing output. See Q4 for reading stdin. See Q134 for try/catch. See Q152 for writing files. See Q153 for file path exists. See Q154 for read file lines.","ek9Example":"defines module qa.fileio.read\n\n  defines function\n\n    processLine() as pure\n      -> line as String\n      <- rtn as String: \"[read] \" + line\n\n  defines program\n\n    ReadTextFileDemo()\n      stdout <- Stdout()\n\n      // === CREATING A TEXTFILE ===\n\n      file <- TextFile(\"/tmp/qa-example-input.txt\")\n      stdout.println(\"File: \" + $file)\n\n      // === READING WITH TRY-WITH-RESOURCES ===\n\n      // If the file exists, input() returns a set stream\n      // The try guard only enters the body if input is set\n      stdout.println(\"Attempting to read file\")\n\n      // === TEXTFILE PROPERTIES ===\n\n      // TextFile has useful query methods\n      stdout.println(\"Is readable: \" + $file.isReadable())\n\n      // === STREAM PROCESSING ===\n\n      // You can also pipe file input through stream operations\n      // cat input | map with processLine | head 5 > stdout","migrationContext":"Java: Files.readString(Path) or BufferedReader with try-with-resources. Python: with open('file') as f: content = f.read(). Rust: fs::read_to_string('file'). Go: os.ReadFile('file'). EK9: TextFile('file') with try -> input <- file.input() for automatic resource management and guard-based missing file handling.","keywords":["content","file","input","load","open","read","stream","text","textfile","write"],"primaryTopics":["read file","file input","open file"],"typicalErrors":[{"error":"E50060","correct":"stdout.println(\"File: \" + $file)","incorrect":"stdout.println(file.toString())","explanation":"TextFile has no toString() method. Use the $ prefix operator for string conversion. See ek9 -h E50060 for details."},{"error":"E50060","correct":"stdout.println(\"Is readable: \" + $file.isReadable())","incorrect":"stdout.println(\"Is readable: \" + $file.canRead())","explanation":"TextFile has no canRead() method. Use isReadable() to check if the file can be read. EK9 uses descriptive method names. See ek9 -h E50060 for details."}],"companions":[]}
{"id":152,"category":"File I/O","question":"How do I write text to a file in EK9?","url":"https://ek9.io/qa/QA0152.html","alternatePhrasings":["How do I save data to a file in EK9?","How do I create and write a text file in EK9?","What is TextFile output in EK9?"],"answer":"EK9 uses TextFile with output() to write text to files. Create a TextFile with the target path, open an output stream inside try-with-resources, and write lines using the stream.\n\nCREATING OUTPUT\nUse file.output() to get a CloseableStringOutput stream:\n  file <- TextFile(\"output.txt\")\n  try\n    -> output <- file.output()\n    output.println(\"Hello, file!\")\nThe output stream is automatically closed when the try block exits.\n\nWRITING LINES\nThe output stream supports println() for writing lines of text. You can write multiple lines in sequence:\n  output.println(\"Line one\")\n  output.println(\"Line two\")\n\nCLOSING RESOURCES\nThe try-with-resources pattern ensures the file is properly flushed and closed, even if an exception occurs during writing. You never need to explicitly close the stream.\n\nTRY WITH RESOURCES FOR OUTPUT\nJust like reading, writing uses the try guard pattern:\n  try\n    -> output <- file.output()\n    // write data here\n  catch\n    -> ex as Exception\n    stderr.println(\"Write failed: \" + $ex)\nIf the file cannot be opened for writing, output() returns an unset value and the try body is skipped.\n\nSee Q151 for reading files. See Q134 for try/catch. See Q3 for stdout.","ek9Example":"defines module qa.fileio.write\n\n  defines program\n\n    WriteTextFileDemo()\n      stdout <- Stdout()\n\n      // === CREATING A TEXTFILE FOR WRITING ===\n\n      file <- TextFile(\"/tmp/qa-example-output.txt\")\n      stdout.println(\"Output file: \" + $file)\n\n      // === FILE PROPERTIES ===\n\n      stdout.println(\"Is writable: \" + $file.isWritable())\n\n      // === WRITING PATTERN ===\n\n      // The standard pattern for writing:\n      //   try\n      //     -> output <- file.output()\n      //     output.println(\"Hello, file!\")\n      //   catch\n      //     -> ex as Exception\n      //     stderr.println(\"Write failed\")\n\n      // TextFile also supports checking file state\n      stdout.println(\"File length: \" + $file.length())","migrationContext":"Java: Files.writeString(Path, content) or BufferedWriter with try-with-resources. Python: with open('file', 'w') as f: f.write(content). Rust: fs::write('file', content). Go: os.WriteFile('file', data, perm). EK9: TextFile('file') with try -> output <- file.output() for safe, auto-closing file writing.","keywords":["create","file","output","read","save","stream","text","textfile","write"],"primaryTopics":["write file","file output","save file"],"typicalErrors":[{"error":"E50060","correct":"stdout.println(\"Output file: \" + $file)","incorrect":"stdout.println(file.toString())","explanation":"TextFile has no toString() method. Use the $ prefix operator for string conversion. See ek9 -h E50060 for details."},{"error":"E50060","correct":"stdout.println(\"Is writable: \" + $file.isWritable())","incorrect":"stdout.println(\"Is writable: \" + $file.canWrite())","explanation":"TextFile has no canWrite() method. Use isWritable() to check if the file can be written. EK9 uses descriptive method names. See ek9 -h E50060 for details."}],"companions":[]}
{"id":153,"category":"File I/O","question":"How do I check if a file or directory exists in EK9?","url":"https://ek9.io/qa/QA0153.html","alternatePhrasings":["How do I use FileSystemPath in EK9?","How do I check if a path exists in EK9?","How do I work with file paths in EK9?"],"answer":"EK9 provides the FileSystemPath type for working with file system paths. It supports checking existence, distinguishing files from directories, and performing path operations.\n\nFILESYSTEMPATH TYPE\nCreate a FileSystemPath from a string:\n  path <- FileSystemPath(\"/home/user/data.txt\")\nFileSystemPath represents a location on the file system without opening or reading the file.\n\nCHECKING EXISTENCE\nUse exists() to check if the path points to something on disk:\n  if path.exists()\n    stdout.println(\"Path exists\")\nThe exists() method returns a Boolean that is set to true or false.\n\nFILE VS DIRECTORY\nDistinguish between files and directories:\n  if path.isFile()\n    stdout.println(\"It is a file\")\n  else if path.isDirectory()\n    stdout.println(\"It is a directory\")\nYou can also check isReadable(), isWritable(), and isExecutable().\n\nPATH OPERATIONS\nFileSystemPath supports several path manipulation methods:\n  abs <- path.absolutePath()\n  name <- path.fileName()\n  parent <- path.parent()\n  joined <- path + \"subdir\"\nUse startsWith() and endsWith() for prefix and suffix checks.\n\nSee Q42 for the Path type. See Q151 for reading files. See Q152 for writing files.","ek9Example":"defines module qa.fileio.path\n\n  defines program\n\n    FilePathExistsDemo()\n      stdout <- Stdout()\n\n      // === CREATING FILESYSTEMPATH ===\n\n      path <- FileSystemPath(\"/tmp\")\n      stdout.println(\"Path: \" + $path)\n\n      // === CHECKING EXISTENCE ===\n\n      stdout.println(\"Exists: \" + $path.exists())\n\n      // === FILE VS DIRECTORY ===\n\n      stdout.println(\"Is file: \" + $path.isFile())\n      stdout.println(\"Is directory: \" + $path.isDirectory())\n\n      // === PATH PROPERTIES ===\n\n      stdout.println(\"Is readable: \" + $path.isReadable())\n      stdout.println(\"Is writable: \" + $path.isWritable())\n      stdout.println(\"Is absolute: \" + $path.isAbsolute())\n\n      // === PATH OPERATIONS ===\n\n      absolutePath <- path.absolutePath()\n      stdout.println(\"Absolute: \" + $absolutePath)\n\n      // Path concatenation with + operator\n      subPath <- path + \"subdir\"\n      stdout.println(\"Sub path: \" + $subPath)","migrationContext":"Java: Files.exists(Path) and Path for path operations. Python: os.path.exists() or pathlib.Path. Rust: Path::exists() and std::path::Path. Go: os.Stat() for existence checks. EK9: FileSystemPath('/path') with exists(), isFile(), isDirectory() methods and + operator for path joining.","keywords":["check","directory","exists","file","filesystem","folder","isDirectory","isFile","path","read","write"],"primaryTopics":["file exists","check file","file path"],"typicalErrors":[{"error":"E50060","correct":"path <- FileSystemPath(\"/tmp\")","incorrect":"path <- FileSystemPath(42)","explanation":"FileSystemPath constructor expects a String argument. Passing an Integer triggers E50060 — constructor not resolved because no FileSystemPath(Integer) exists. File paths are always represented as strings in EK9. See ek9 -h E50060 for details."},{"error":"E50060","correct":"stdout.println(\"Exists: \" + $path.exists())","incorrect":"stdout.println(\"Exists: \" + $path.fileExists())","explanation":"FileSystemPath has no fileExists() method. Use exists() to check if the path points to something on disk. See ek9 -h E50060 for details."},{"error":"E50060","correct":"absolutePath <- path.absolutePath()","incorrect":"absolutePath <- path.getAbsolutePath()","explanation":"FileSystemPath has no getAbsolutePath() method. Use absolutePath() to get the absolute path. EK9 uses descriptive method names rather than Java-style getters. See ek9 -h E50060 for details."}],"companions":[]}
{"id":154,"category":"File I/O","question":"How do I read a file line by line in EK9?","url":"https://ek9.io/qa/QA0154.html","alternatePhrasings":["How do I process a file line by line in EK9?","How do I stream file contents in EK9?","How do I use cat with file input in EK9?"],"answer":"EK9 treats file input as a stream source. Open a file with TextFile.input() inside a try block, then use cat to pipe lines through stream operations like filter, map, and collect.\n\nSTREAMING FILE INPUT\nOpen the file and stream its contents:\n  file <- TextFile(\"data.txt\")\n  try\n    -> input <- file.input()\n    cat input > stdout\nEach line from the file flows through the pipeline as a String.\n\nCAT WITH FILE INPUT\nThe cat operation treats the input stream as a source of lines:\n  cat input | map with processLine > stdout\nThis sends each line through the processLine function before printing.\n\nFILTER AND TRANSFORM LINES\nCombine file streaming with pipeline operations:\n  cat input | filter by notEmpty | map with trim > stdout\nThis skips empty lines and trims whitespace from each remaining line.\n\nCOLLECT LINES INTO LIST\nGather all lines into a collection:\n  cat input | collect as List of String\nThis reads every line and stores them in a List for further processing.\n\nSee Q89 for stream pipelines. See Q151 for reading files. See Q125 for head/tail/skip.","ek9Example":"defines module qa.fileio.lines\n\n  defines function\n\n    notEmpty() as pure\n      -> line as String\n      <- rtn as Boolean: line?\n\n    addLineNumber() as pure\n      -> line as String\n      <- rtn as String: \">> \" + line\n\n  defines program\n\n    ReadFileLinesDemo()\n      stdout <- Stdout()\n\n      // === TEXTFILE FOR LINE READING ===\n\n      file <- TextFile(\"/tmp/qa-example-lines.txt\")\n      stdout.println(\"File for line reading: \" + $file)\n\n      // === STREAMING PATTERN ===\n\n      // The standard pattern for line-by-line processing:\n      //   try\n      //     -> input <- file.input()\n      //     cat input | filter by notEmpty | map with addLineNumber > stdout\n\n      // === FILTER AND TRANSFORM ===\n\n      // Stream operations work naturally with file input:\n      //   cat input | filter by notEmpty | head 10 > stdout\n\n      // === COLLECT INTO LIST ===\n\n      // Gather lines into a collection:\n      //   cat input | collect as List of String\n\n      stdout.println(\"Line-by-line processing uses cat with file input streams\")","migrationContext":"Java: Files.lines(Path) returns a Stream of String for lazy line-by-line processing. Python: for line in open('file') iterates lazily. Rust: BufRead::lines() returns an iterator of lines. Go: bufio.Scanner for line-by-line reading. EK9: cat input with TextFile.input() in a stream pipeline, supporting filter, map, collect and all stream operations.","keywords":["cat","file","filter","iterate","line","lines","pipeline","read","stream","write"],"primaryTopics":[],"typicalErrors":[{"error":"E50060","correct":"stdout.println(\"File for line reading: \" + $file)","incorrect":"stdout.println(file.toString())","explanation":"TextFile has no toString() method. Use the $ prefix operator for string conversion. See ek9 -h E50060 for details."},{"error":"E50060","correct":"stdout.println(\"Line-by-line processing uses cat with file input streams\")","incorrect":"stdout.println(file.readLines())","explanation":"TextFile has no readLines() method. Use file.input() inside a try block to get a stream for line-by-line processing. See ek9 -h E50060 for details."}],"companions":[]}
{"id":155,"category":"Testing","question":"How do I write a unit test in EK9?","url":"https://ek9.io/qa/QA0155.html","alternatePhrasings":["How do I create tests in EK9?","What is the @Test directive in EK9?","Does EK9 have a built-in test framework?"],"answer":"EK9 has built-in testing support with no external framework needed. Mark any program with @Test to make it a test. Group related tests with @Test: \"groupName\".\n\nTHE @TEST DIRECTIVE\nPlace @Test on its own line before a program to mark it as a test:\n  defines program\n    @Test\n    SimpleTest()\n      stdout <- Stdout()\n      stdout.println(\"Test passed\")\nThe @Test annotation must be on its own line, not inline.\n\nTEST PROGRAMS\nTests are just programs. They run in the same way as normal programs but are collected and executed by the test runner. Any program marked @Test is included when you run ek9 -t file.ek9.\n\nTEST GROUPS\nGroup related tests with @Test: \"groupName\":\n  @Test: \"database\"\n  DbConnectionTest()\n    // test database connection\n  @Test: \"database\"\n  DbQueryTest()\n    // test queries\nRun a specific group with ek9 -tg database file.ek9. Tests in the same group run sequentially.\n\nBLACK BOX TESTS\nFor output-based testing, print expected values to stdout and compare against an expected_output.txt file. This avoids assert bytecode contamination and provides clean test verification.\n\nNO EXTERNAL FRAMEWORK NEEDED\nUnlike Java (JUnit), Python (pytest), or Rust (#[test] with cargo), EK9 testing is built into the language. No dependencies to add, no test runner to install.\n\nSee Q156 for assertions. See Q157 for running tests. See Q2 for compile and run. See Q204 for black-box testing. See Q209 for exception testing.","ek9Example":"defines module qa.testing.basics\n\n  defines program\n\n    // === BASIC TEST ===\n\n    @Test\n    SimpleArithmeticTest()\n      stdout <- Stdout()\n      result <- 2 + 3\n      assert result == 5\n      stdout.println(\"2 + 3 = \" + $result)\n\n    // === GROUPED TESTS ===\n\n    @Test: \"strings\"\n    StringConcatenationTest()\n      stdout <- Stdout()\n      full <- `Hello World`\n      assert full == \"Hello World\"\n      stdout.println(\"Concatenation: \" + full)\n\n    @Test: \"strings\"\n    StringLengthTest()\n      stdout <- Stdout()\n      text <- \"EK9\"\n      assert text.length() == 3\n      stdout.println(\"Length: \" + $text.length())\n\n    // === TEST WITH SETUP ===\n\n    @Test\n    ListOperationsTest()\n      stdout <- Stdout()\n      items <- List() of Integer\n      items += 10\n      items += 20\n      items += 30\n      assert items.length() == 3\n      stdout.println(\"List size: \" + $items.length())","migrationContext":"Java: JUnit @Test annotation with separate framework dependency. Python: pytest or unittest with class-based test structure. Rust: #[test] attribute with cargo test runner. Go: func TestXxx(t *testing.T) naming convention. EK9: built-in @Test directive on programs, no framework dependency, optional grouping with @Test: \"group\".","keywords":["assert","built-in","directive","framework","group","migrate","program","test","testing","unit","verify"],"primaryTopics":["unit test","write test","testing"],"typicalErrors":[{"error":"E50060","correct":"stdout.println(\"2 + 3 = \" + $result)","incorrect":"stdout.println(result.toString())","explanation":"Integer has no toString() method. Use the $ prefix operator for string conversion or string interpolation. See ek9 -h E50060 for details."},{"error":"E50060","correct":"stdout.println(\"Length: \" + $text.length())","incorrect":"stdout.println(\"Length: \" + $text.size())","explanation":"String has no size() method. Use length() to get the number of characters in a string. See ek9 -h E50060 for details."}],"companions":[]}
{"id":156,"category":"Testing","question":"How do I assert conditions in a test in EK9?","url":"https://ek9.io/qa/QA0156.html","alternatePhrasings":["What assertion keywords does EK9 have?","How do I use assert and require in EK9?","What is the difference between assert and require in EK9?"],"answer":"EK9 provides assert for test assertions and require for production preconditions. Both are built-in keywords, not library functions.\n\nASSERT KEYWORD\nUse assert in test programs to verify conditions:\n  assert result == 5\n  assert name?\n  assert items.length() > 0\nAssert is only allowed in test context (programs marked with @Test). It throws an exception if the condition is false.\n\nREQUIRE FOR PRECONDITIONS\nUse require in production code for preconditions:\n  require name?\n  require age > 0\nRequire works in any context and enforces that callers provide valid inputs.\n\nASSERT VS REQUIRE\nassert is for test verification: 'did the code produce the right result?'\nrequire is for defensive programming: 'are the inputs valid before proceeding?'\nAssert is restricted to test programs. Require works everywhere.\n\nOUTPUT-BASED TESTING AS ALTERNATIVE\nFor cleaner tests, print values to stdout and compare against expected_output.txt files. This avoids assert bytecode contamination in @BYTECODE directives and provides two independent validations: output verifies runtime behavior, directives verify code generation.\n\nSee Q155 for writing tests. See Q134 for try/catch. See Q135 for throwing exceptions. See Q204 for black-box testing. See Q209 for exception testing.\n\nSee Q305 for require vs assert vs throw. See Q306 for assertThrows. See Q307 for assertDoesNotThrow.","ek9Example":"defines module qa.testing.assertions\n\n  defines function\n\n    add() as pure\n      ->\n        a as Integer\n        b as Integer\n      <- rtn as Integer: a + b\n\n    isPositive() as pure\n      -> number as Integer\n      <- rtn as Boolean: number > 0\n\n  defines program\n\n    // === ASSERT IN TESTS ===\n\n    @Test\n    BasicAssertTest()\n      stdout <- Stdout()\n\n      result <- add(2, 3)\n      assert result == 5\n\n      assert isPositive(42)\n\n      name <- \"EK9\"\n      assert name?\n\n      stdout.println(\"All assertions passed\")\n\n    // === REQUIRE FOR PRECONDITIONS ===\n\n    @Test\n    RequireTest()\n      stdout <- Stdout()\n\n      count <- 10\n      require count > 0\n\n      text <- \"Hello\"\n      require text?\n\n      assert count == 10\n      assert text == \"Hello\"\n      stdout.println(\"All preconditions met\")\n\n    // === OUTPUT-BASED ALTERNATIVE ===\n\n    @Test\n    OutputBasedTest()\n      stdout <- Stdout()\n\n      // Print values and compare against expected_output.txt\n      result <- add(10, 20)\n      assert result == 30\n      stdout.println(\"Result: \" + $result)","migrationContext":"Java: assertEquals/assertTrue from JUnit, assertThrows for exceptions. Python: assert statement or pytest.raises for exceptions. Rust: assert!, assert_eq!, assert_ne! macros. Go: t.Error() and t.Fatal() on testing.T. EK9: built-in assert keyword for tests, require keyword for preconditions, plus output-based testing as a cleaner alternative.","keywords":["assert","assertion","check","condition","precondition","require","test","throws","verify"],"primaryTopics":["assert","assertion","test assertion"],"typicalErrors":[{"error":"E07530","correct":"assert result == 5","incorrect":"assert result","explanation":"The assert keyword requires a Boolean expression. Passing an Integer or String directly triggers E07530 — only compatible with Boolean type. Use a comparison operator to produce a Boolean. See ek9 -h E07530 for details."},{"error":"E50060","correct":"stdout.println(\"All assertions passed\")","incorrect":"stdout.println(result.toString())","explanation":"Integer has no toString() method. Use the $ prefix operator for string conversion or string interpolation. See ek9 -h E50060 for details."},{"error":"E50060","correct":"stdout.println(\"Result: \" + $result)","incorrect":"stdout.println(result.valueOf())","explanation":"Integer has no valueOf() method. Use the $ prefix operator or string concatenation with + for string conversion. See ek9 -h E50060 for details."}],"companions":[]}
{"id":157,"category":"Testing","question":"How do I run tests and see results in EK9?","url":"https://ek9.io/qa/QA0157.html","alternatePhrasings":["How do I run EK9 tests from the command line?","How do I check test coverage in EK9?","What test output formats does EK9 support?"],"answer":"EK9 provides a built-in test runner with multiple output formats and coverage reporting. Use the -t flag to run tests from the command line.\n\nTHE -t FLAG\nRun all tests in a file:\n  ek9 -t file.ek9\nThis runs every @Test program and reports results in human-readable format.\n\nOUTPUT FORMATS\nEK9 supports multiple output formats:\n  ek9 -t file.ek9    # Human-readable (default)\n  ek9 -t0 file.ek9   # Terse (pass/fail only)\n  ek9 -t2 file.ek9   # JSON (for AI/tool parsing)\n  ek9 -t3 file.ek9   # JUnit XML (for CI integration)\n\nRUNNING TEST GROUPS\nRun a specific test group:\n  ek9 -tg database file.ek9\nThis runs only tests marked with @Test: \"database\".\n\nLISTING TESTS\nList all tests without running them:\n  ek9 -tL file.ek9\n\nCOVERAGE\nRun tests with coverage reporting:\n  ek9 -tC file.ek9   # Coverage summary after tests\n  ek9 -t4 file.ek9   # Detail JSON coverage\n  ek9 -t5 file.ek9   # Verbose coverage\n  ek9 -t6 file.ek9   # HTML coverage report\nCoverage must be at least 80% for packaging with -P.\n\nEXIT CODES\nThe test runner uses specific exit codes:\n  0  = all tests pass, coverage OK\n  11 = one or more tests failed\n  12 = tests pass but coverage below 80%\n\nSee Q155 for writing tests. See Q156 for assertions. See Q2 for compile and run. See Q204 for black-box testing. See Q206 for test coverage. See Q322 for profiling data. See Q628 for profiling tests. See Q749 for fuzzing overview. See Q754 for automatic test generation.","ek9Example":"defines module qa.testing.running\n\n  defines function\n\n    multiply() as pure\n      ->\n        first as Integer\n        second as Integer\n      <- rtn as Integer: first * second\n\n  defines program\n\n    // === BASIC TEST ===\n\n    @Test\n    MultiplyTest()\n      stdout <- Stdout()\n      result <- multiply(6, 7)\n      assert result == 42\n      stdout.println(\"6 * 7 = \" + $result)\n\n    // === GROUPED TEST ===\n\n    // Run this group only with: ek9 -tg math file.ek9\n    @Test: \"math\"\n    AdditionTest()\n      stdout <- Stdout()\n      assert 2 + 2 == 4\n      stdout.println(\"Addition test passed\")\n\n    @Test: \"math\"\n    SubtractionTest()\n      stdout <- Stdout()\n      assert 10 - 3 == 7\n      stdout.println(\"Subtraction test passed\")\n\n    // === DEMONSTRATING TEST STRUCTURE ===\n    // Run all tests:       ek9 -t file.ek9\n    // Run math group only: ek9 -tg math file.ek9\n    // JSON output:         ek9 -t2 file.ek9\n    // JUnit XML output:    ek9 -t3 file.ek9\n    // With coverage:       ek9 -tC file.ek9","migrationContext":"Java: mvn test or gradle test with JUnit, JaCoCo for coverage. Python: pytest with pytest-cov for coverage. Rust: cargo test with tarpaulin for coverage. Go: go test -v with go test -cover. EK9: built-in ek9 -t runner with JSON/JUnit XML output formats and integrated coverage via -tC flags, no external tools needed.","keywords":["assert","compile","coverage","format","group","json","junit","output","result","results","run","runner","test","verify"],"primaryTopics":["run tests","test runner"],"typicalErrors":[{"error":"E50060","correct":"result <- multiply(6, 7)","incorrect":"result <- multiply(6, 7).intValue()","explanation":"Integer has no intValue() method. multiply() already returns an Integer. See ek9 -h E50060 for details."},{"error":"E50060","correct":"stdout.println(\"6 * 7 = \" + $result)","incorrect":"stdout.println(result.toString())","explanation":"Integer has no toString() method. Use the $ prefix operator for string conversion or string concatenation. See ek9 -h E50060 for details."}],"companions":[]}
{"id":158,"category":"Concurrency","question":"How do I protect shared data from concurrent access in EK9?","url":"https://ek9.io/qa/QA0158.html","alternatePhrasings":["How do I use MutexLock in EK9?","What is thread-safe data access in EK9?","How does MutexLock protect shared state in EK9?"],"answer":"EK9 uses MutexLock of T to protect shared data from concurrent access. The canonical pattern is to hold the MutexLock as a FIELD on a wrapping class, and expose access through methods that internally use MutexKey callbacks. The lock itself never escapes the wrapping class — it has a stable field-rooted home that the compiler tracks for data-race and deadlock detection.\n\nFIELD-ON-CLASS PATTERN\nDeclare the MutexLock as a field with an inline initialiser:\n  Counter\n    lockedCount as MutexLock of Integer: MutexLock(0)\n\nThe field initialiser is the only legal MutexLock construction site (E08254/E08256 reject locals and collections of locks).\n\nMUTEXKEY CALLBACK PATTERN\nInside each method that needs to read or update the protected value, declare a dynamic MutexKey function. The function body is the critical section; lockedItem is the protected value while the lock is held:\n  read()\n    <- rtn as Integer: Integer()\n    accessKey <- (rtn) is MutexKey of Integer as function\n      rtn :=: lockedItem\n    require lockedCount.enter(accessKey)\n\nWhen the callback returns, the lock is released automatically. This prevents forgetting to unlock.\n\nENTER VS TRYENTER\nenter() blocks until the lock is available. tryEnter() attempts to acquire the lock and returns immediately with a Boolean indicating success:\n  blocking <- lockedCount.enter(keyFunction)\n  attempt <- lockedCount.tryEnter(keyFunction)\n\nCOPY NOT REASSIGN\nInside a MutexKey callback, use the copy operator :=: to update lockedItem, not regular assignment :=. Reassignment would leave an outside reference to the original value, bypassing the lock.\n\nWHY FIELD-ON-CLASS\nEK9's compile-time data-race detection needs every lock to have a stable field-rooted home. Locks declared as locals, returned from functions, or held inside collections defeat the static analysis. Wrapping the lock in a class also keeps the API surface small — callers see only the protected operations.\n\nSee Q117 for singleton and DI for shared components. See Q159 for parallel processing. See Q160 for the concurrency model. See Q213 for MutexLock design pattern.","ek9Example":"defines module qa.concurrency.mutex\n\n  defines class\n\n    //Canonical pattern: MutexLock as a field on a wrapping class.\n    //The class exposes protected operations as methods; the lock\n    //never escapes.\n    Counter\n      lockedCount as MutexLock of Integer: MutexLock(0)\n\n      read()\n        <- rtn as Integer: Integer()\n\n        accessKey <- (rtn) is MutexKey of Integer as function\n          rtn :=: lockedItem\n\n        require lockedCount.enter(accessKey)\n\n      increment()\n        -> amount as Integer\n\n        accessKey <- (amount) is MutexKey of Integer as function\n          lockedItem :=: lockedItem + amount\n\n        require lockedCount.enter(accessKey)\n\n      default operator ?\n\n  defines program\n\n    MutexLockDemo()\n      stdout <- Stdout()\n\n      //Construct the wrapper — the MutexLock is created by the\n      //class's field initialiser, not here in user code.\n      counter <- Counter()\n      stdout.println(`Initial count: ${counter.read()}`)\n\n      counter.increment(5)\n      stdout.println(`After +5: ${counter.read()}`)\n\n      counter.increment(10)\n      stdout.println(`After +10: ${counter.read()}`)","migrationContext":"Java: synchronized blocks or ReentrantLock with try/finally. Python: threading.Lock with context manager. Rust: Mutex<T> with lock() returning a guard. Go: sync.Mutex with Lock()/Unlock(). EK9: MutexLock of T held as a class field, accessed via MutexKey callback inside class methods. The field-rooted home enables compile-time data-race and deadlock detection that other languages lack.","keywords":["class","concurrent","critical","field","lock","mutex","mutexlock","parallel","protect","safety","section","shared","thread"],"primaryTopics":["mutex","lock","thread safety","synchronization"],"typicalErrors":[{"error":"E08256","correct":"counter <- Counter()","incorrect":"counter <- Counter()\n      strayLock <- MutexLock(0)\n      assert strayLock?","explanation":"MutexLock must be declared as a field on a class with an inline initialiser. Constructing one as a local (here `strayLock <- MutexLock(0)`) is rejected at SYMBOL_DEFINITION (E08256). The lock must have a stable field-rooted home so the compiler can track its identity for data-race and deadlock analysis."},{"error":"E08254","correct":"counter <- Counter()","incorrect":"counter <- Counter()\n      badLocks <- List() of MutexLock of Integer\n      assert badLocks?","explanation":"MutexLock cannot appear as a type argument to another generic (here `List of MutexLock`). Use one MutexLock per shared payload, held as a field, rather than a collection of locks. See ek9 -h E08254 for details."}],"companions":[]}
{"id":159,"category":"Concurrency","question":"How do I run code concurrently or in parallel in EK9?","url":"https://ek9.io/qa/QA0159.html","alternatePhrasings":["How do I process items in parallel in EK9?","What is async in EK9 stream pipelines?","How does EK9 handle parallel processing?"],"answer":"EK9 uses the async operation in stream pipelines for parallel processing. Instead of managing threads directly, you express parallelism as a pipeline stage that invokes each stream element concurrently.\n\nSTREAM PIPELINES WITH ASYNC\nThe stream must contain zero-arg function delegates. The async stage invokes each one in parallel on a virtual thread; the return value flows into the next stage:\n  cat workers | async | map with toLine > stdout\nasync itself takes NO function parameter — it calls the stream element. There is no 'async with processor' form; the function to call IS the stream element.\n\nTHE ASYNC OPERATION\nasync is a stream pipeline operation like call, filter, or map. The stream type must be a zero-arg function delegate that returns a mainValue; that return value becomes the next stream's element type. async only differs from call in that elements are dispatched concurrently to virtual threads.\n\nWHEN TO USE ASYNC\nUse async for I/O-bound or computation-heavy operations where items can be processed independently:\n- Fetching multiple URLs (build a List of url-fetcher functions, then `| async`)\n- Processing multiple files (List of file-reader functions, then `| async`)\n- Making parallel API calls (List of request functions, then `| async`)\n\nTURNING DATA INTO ASYNC WORK\nIf you start with data rather than function delegates, use `map with` to project each item to a zero-arg function delegate, then `| async` to invoke them in parallel:\n  cat urls | map with toFetcher | async | filter by isSuccess > stdout\nHere toFetcher takes a URL and returns a zero-arg function whose body actually performs the fetch. The async stage then runs all those fetchers concurrently.\n\nVIRTUAL THREADS\nEK9 runs on Java 25 with virtual threads. The async operation leverages virtual threads for lightweight concurrency. You get parallelism without managing thread pools or executors.\n\nTHREAD-BOUNDARY IMPLICATIONS\nEach `| async` invocation is a NEW thread context. If a worker captures a MutexLock that the calling pipeline is already holding, you will get E08253 (cross-thread same lock) because MutexLock reentrancy is per-thread. See Q1299 for the recovery pattern.\n\nSee Q89 for stream pipelines. See Q158 for MutexLock (which shows the canonical LockableAddressSet pattern). See Q160 for concurrency model. See Q1299 for the async + same-lock pitfall. See Q125 for head/tail.","ek9Example":"defines module qa.concurrency.parallel\n\n  defines function\n\n    doubleIt() as pure\n      -> number as Integer\n      <- rtn as Integer: number * 2\n\n    isLarge() as pure\n      -> number as Integer\n      <- rtn <- Boolean()\n      batchSize <- 50\n      rtn: number > batchSize\n\n  defines program\n\n    ParallelProcessingDemo()\n      stdout <- Stdout()\n\n      // === STREAM PIPELINE ===\n\n      items <- List() of Integer\n      items += 10\n      items += 20\n      items += 30\n      items += 40\n      items += 50\n\n      // Standard pipeline (sequential)\n      cat items | map with doubleIt | filter by isLarge > stdout\n\n      // For parallel: stream must contain zero-arg function delegates.\n      //   cat workers | async | map with toLine > stdout\n      // async takes NO parameter — the function IS the stream element.\n      // To start from data: cat urls | map with toFetcher | async > stdout\n\n      // See Q158 for the canonical async-safe shared-state\n      // pattern (LockableAddressSet via MutexLock of AddressSet).\n\n      stdout.println(\"EK9 uses async in stream pipelines for parallelism\")","migrationContext":"Java: ExecutorService, CompletableFuture, or parallel streams. Python: asyncio, multiprocessing, or concurrent.futures. Rust: async/await with tokio, or rayon for data parallelism. Go: goroutines with channels. EK9: async in stream pipelines (cat funcs | async) where each stream element is a zero-arg function delegate that async invokes in parallel — leveraging Java 25 virtual threads for lightweight concurrency. No coloured-function problem; no thread pool wiring.","keywords":["async","compile","concurrency","concurrent","migrate","parallel","pipeline","process","stream","thread","virtual"],"primaryTopics":["parallel","concurrent","parallel processing"],"typicalErrors":[{"error":"E50060","correct":"cat items | map with doubleIt | filter by isLarge > stdout","incorrect":"items.stream().map(doubleIt).filter(isLarge).forEach(stdout)","explanation":"EK9 has no .stream() method chains. Use pipe syntax: 'cat items | map with fn | filter by fn > stdout'. EK9 expresses pipelines through operators, not method calls. See ek9 -h E50060 for details."},{"error":"E50060","correct":"items += 10","incorrect":"items.add(10)","explanation":"EK9 List has no .add() method. Use the += operator. See ek9 -h E50060 for details."}],"companions":[]}
{"id":160,"category":"Concurrency","question":"Does EK9 have async/await, goroutines, or manual thread management?","url":"https://ek9.io/qa/QA0160.html","alternatePhrasings":["Why doesn't EK9 have async/await?","How is EK9's concurrency model different from Go or Rust?","Does EK9 support goroutines or channels?"],"answer":"EK9 does not have async/await, goroutines, channels, or manual thread management. Instead, it treats concurrency as a pipeline problem solved with stream operations and MutexLock.\n\nWHAT EK9 DOES NOT HAVE\nEK9 deliberately excludes:\n- async/await syntax (no colored function problem)\n- Goroutines or lightweight coroutines\n- Channels or message-passing primitives\n- Thread.start() or manual thread creation\n- Future/Promise types as first-class constructs\n\nWHY NO ASYNC/AWAIT OR GOROUTINES\nasync/await creates the 'colored function' problem where async functions cannot be called from sync functions without propagating async throughout the call chain. Goroutines and channels add complexity for coordination. EK9 avoids both by making concurrency a pipeline concern, not a function-level concern.\n\nTHE EK9 APPROACH\nEK9 provides two concurrency mechanisms:\n1. Stream pipelines where the stream type is a zero-arg function delegate and `| async` invokes each element in parallel on virtual threads: `cat workers | async | map with toLine > stdout`. async takes NO function argument — the function IS the stream element.\n2. MutexLock of T for protecting shared state, held as a field on a wrapping class so the lock has a stable identity for compile-time data-race and deadlock detection.\nThese two primitives cover the vast majority of concurrent programming needs.\n\nVIRTUAL THREADS\nEK9 runs on Java 25 virtual threads. Virtual threads are extremely lightweight (millions per process) and handle blocking I/O efficiently. The runtime manages scheduling automatically.\n\nCOMPILE-TIME SAFETY\nThe pairing of MutexLock + closed-world call graph + statically-derivable lock identity lets the compiler prove the absence of data races (E08251), lock-order deadlocks (E08252), same-lock-across-thread-boundary issues (E08253), and same-type lock nesting whose acquisition order cannot be statically proven (E08255 — the bank-transfer / dining-philosophers shape) WITHOUT runtime overhead and WITHOUT developer annotations. No other mainstream language does this at compile time.\n\nCONCURRENCY IS A PIPELINE PROBLEM\nMost concurrency is about processing items in parallel or protecting shared data. Pipelines handle the first, MutexLock handles the second. No thread pools, no executors, no callback hell.\n\nDESIGN PHILOSOPHY\nLike removing break/continue/return, EK9 removes low-level concurrency primitives that cause bugs. Thread safety violations, deadlocks, and race conditions are among the hardest bugs to diagnose. Simpler primitives produce more reliable concurrent programs.\n\nSee Q158 for MutexLock (which shows the canonical LockableAddressSet pattern). See Q159 for async pipelines. See Q1298 for lock-order cycle deadlock recovery. See Q1299 for the async + same-lock pitfall. See Q1300 for multi-lock design. See Q89 for stream pipelines. See Q144 for control flow design philosophy.","ek9Example":"defines module qa.concurrency.philosophy\n\n  defines function\n\n    transform() as pure\n      -> number as Integer\n      <- rtn as String: \"processed: \" + $number\n\n  defines program\n\n    ConcurrencyModelDemo()\n      stdout <- Stdout()\n\n      // === THE EK9 APPROACH ===\n\n      // EK9 treats concurrency as a pipeline problem:\n\n      // 1. Stream pipelines for parallel processing\n      items <- List() of Integer\n      items += 1\n      items += 2\n      items += 3\n\n      cat items | map with transform > stdout\n\n      // For parallel: cat workers | async | map with toLine > stdout\n      // (stream type IS the function — async takes no parameter)\n\n      // 2. MutexLock as a field — see Q158 for the canonical\n      // LockableAddressSet pattern.\n\n      // EK9 has no async/await, no goroutines, no Thread.start(),\n      // no Future. Runs on Java 25 virtual threads.\n\n      stdout.println(\"EK9 concurrency: pipelines + MutexLock, no thread management\")","migrationContext":"Java: CompletableFuture, ExecutorService, synchronized, virtual threads. Python: asyncio with async/await. Rust: async/await with tokio or async-std runtimes. Go: goroutines with channels and select. Kotlin: coroutines with suspend functions. EK9: stream pipelines with async operation and MutexLock, running on Java 25 virtual threads with no explicit thread management.","keywords":["async","await","channel","concurrency","design","future","goroutine","migrate","model","parallel","philosophy","promise","thread"],"primaryTopics":[],"typicalErrors":[{"error":"E50060","correct":"cat items | map with transform > stdout","incorrect":"items.stream().map(transform).forEach(stdout)","explanation":"EK9 has no .stream() method chains. Use pipe syntax: 'cat items | map with fn > stdout'. Concurrency is a pipeline problem in EK9. See ek9 -h E50060 for details."},{"error":"E50060","correct":"items += 1","incorrect":"items.add(1)","explanation":"EK9 List has no .add() method. Use the += operator. See ek9 -h E50060 for details."}],"companions":[]}
{"id":161,"category":"Safe Value Access","question":"How do I safely get a value from a Dict when the key might not exist?","url":"https://ek9.io/qa/QA0161.html","alternatePhrasings":["How do I avoid exceptions when accessing a Dict key in EK9?","What is the safe way to look up a Dict value in EK9?","How do I handle missing Dict keys in EK9?"],"answer":"EK9 Dicts use getOrDefault(key, default) for safe value access. There is no .get() method that could return an unset value. This design eliminates null pointer exceptions from dictionary lookups.\n\nGETORDEFAULT\nAlways returns a usable value:\n  age <- ages.getOrDefault(\"Alice\", 0)\nIf the key exists, returns its value. If missing, returns the default you provide. No exceptions, no unset results.\n\nCONTAINS CHECK\nTest for key existence before access:\n  if ages contains \"Alice\"\n    stdout.println(\"Alice is in the dict\")\nThe contains operator checks keys, not values.\n\nGUARD PATTERN WITH FOR\nIterate safely over entries without worrying about missing keys:\n  for entry in ages\n    stdout.println(`${entry.key()}: ${entry.value()}`)\nEvery entry yielded by iteration is guaranteed to be set.\n\nIS IN / IS NOT IN\nNatural language syntax for key checks:\n  if \"Bob\" is in ages\n    stdout.println(\"Found Bob\")\n  if \"Zara\" is not in ages\n    stdout.println(\"Zara not found\")\n\nSee Q46 for Dict basics. See Q90 for Dict operations. See Q129 for missing key patterns. See Q165 for why EK9 has no .get() method. See Q243 for coalescing operators (??, ?:, <?, >?) used for safe value selection.","ek9Example":"defines module qa.safeaccess.dictsafe\n\n  defines program\n    DictSafeAccessDemo()\n      stdout <- Stdout()\n\n      ages <- {\"Alice\": 30, \"Bob\": 25, \"Charlie\": 35}\n\n      // === GETORDEFAULT ===\n\n      // Key exists — returns actual value\n      aliceAge <- ages.getOrDefault(\"Alice\", 0)\n      stdout.println(`Alice age: ${aliceAge}`)\n\n      // Key missing — returns default\n      missingAge <- ages.getOrDefault(\"Zara\", 0)\n      stdout.println(`Missing age (default 0): ${missingAge}`)\n\n      // === CONTAINS CHECK ===\n\n      if ages contains \"Alice\"\n        stdout.println(\"Alice is in the dict\")\n\n      if ages contains \"Unknown\"\n        stdout.println(\"Should not print\")\n\n      // === IS IN / IS NOT IN ===\n\n      if \"Bob\" is in ages\n        stdout.println(\"Found Bob\")\n\n      if \"Zara\" is not in ages\n        stdout.println(\"Zara not found\")\n\n      // === SAFE ITERATION ===\n\n      // Every entry from iteration is guaranteed set\n      for entry in ages\n        stdout.println(`${entry.key()}: ${entry.value()}`)\n\n      // === COMBINING PATTERNS ===\n\n      // Use getOrDefault for computation with fallback\n      score <- ages.getOrDefault(\"Dave\", -1)\n      stdout.println(`Dave score (or -1): ${score}`)","migrationContext":"Java: map.get(key) returns null if missing (NPE risk), map.getOrDefault(key, default) exists but is rarely used. Python: dict[key] throws KeyError, dict.get(key, default) is the safe alternative. Rust: HashMap.get() returns Option, must unwrap or match. Go: val, ok := m[key] comma-ok idiom, zero value if missing. Kotlin: map[key] returns null, map.getOrDefault() available. EK9: only getOrDefault() exists, no .get() that could return unset, contains for key checks.","keywords":["access","contains","default","dict","exception","getOrDefault","guard","key","lookup","missing","null","null-safe","safe"],"primaryTopics":["dict safe access","missing key","map lookup"],"typicalErrors":[{"error":"E50060","correct":"aliceAge <- ages.getOrDefault(\"Alice\", 0)","incorrect":"aliceAge <- ages.get(\"Alice\")","explanation":"Dict has no get() method that could return an unset value. Use getOrDefault(key, default) which always returns a usable value. This prevents null/unset bugs from missing key lookups. See ek9 -h E50060 for details."},{"error":"E50060","correct":"score <- ages.getOrDefault(\"Dave\", -1)","incorrect":"score <- ages.getValue(\"Dave\")","explanation":"Dict has no getValue() method. Use getOrDefault(key, default) which always returns a usable value. See ek9 -h E50060 for details."}],"companions":[]}
{"id":162,"category":"Safe Value Access","question":"How do I safely access a List element by index?","url":"https://ek9.io/qa/QA0162.html","alternatePhrasings":["How do I avoid index out of bounds in EK9?","What is the safe way to get an element from a List in EK9?","How do I access list elements without exceptions in EK9?"],"answer":"EK9 Lists use getOrDefault(index, default) for safe element access. If the index is within bounds, returns the element. If out of bounds, returns the default. No exceptions.\n\nGETORDEFAULT BY INDEX\nAlways returns a usable value:\n  first <- numbers.getOrDefault(0, -1)\n  missing <- numbers.getOrDefault(99, -1)\nNo IndexOutOfBoundsException, no null, no crashes.\n\nLENGTH CHECK\nCheck bounds manually when needed:\n  if length numbers > 0\n    stdout.println(`Has items`)\n\nEMPTY CHECK\nTest whether a list has any elements:\n  if numbers is empty\n    stdout.println(\"No items\")\n\nSAFE ITERATION\nIterating with for-in never goes out of bounds:\n  for item in numbers\n    stdout.println($item)\nEach item yielded is guaranteed to be set.\n\nSTREAM HEAD FOR FIRST ELEMENT\nUse head 1 to safely get the first element:\n  cat numbers | head 1 | collect as List of Integer\nReturns an empty list if the source is empty.\n\nSee Q45 for List basics. See Q88 for List operations. See Q125 for head/tail/skip in streams.","ek9Example":"defines module qa.safeaccess.listsafe\n\n  defines function\n    getList()\n      <- rtn <- List() of Integer\n\n  defines program\n    ListSafeAccessDemo()\n      stdout <- Stdout()\n\n      numbers <- [10, 20, 30, 40, 50]\n\n      // === GETORDEFAULT BY INDEX ===\n\n      // Valid index — returns element\n      first <- numbers.getOrDefault(0, -1)\n      stdout.println(`First: ${first}`)\n\n      third <- numbers.getOrDefault(2, -1)\n      stdout.println(`Third: ${third}`)\n\n      // Out of bounds — returns default\n      outOfBounds <- numbers.getOrDefault(99, -1)\n      stdout.println(`Out of bounds (default -1): ${outOfBounds}`)\n\n      // === LENGTH CHECK ===\n\n      listLen <- length numbers\n      stdout.println(`Length: ${listLen}`)\n\n      if length numbers > 0\n        stdout.println(\"List has items\")\n\n      // === EMPTY CHECK ===\n\n      emptyList <- getList()\n      if emptyList is empty\n        stdout.println(\"Empty list detected\")\n\n      if ~numbers is empty\n        stdout.println(\"Numbers is not empty\")\n\n      // === SAFE ITERATION ===\n\n      for item in numbers\n        stdout.println(`Item: ${item}`)\n\n      // === STREAM HEAD FOR FIRST ELEMENT ===\n\n      firstItems <- cat numbers | head 1 | collect as List of Integer\n      stdout.println(`Head 1: ${firstItems}`)\n\n      // Head on empty list yields empty result\n      noItems <- cat emptyList | head 1 | collect as List of Integer\n      stdout.println(`Head of empty: ${noItems}`)","migrationContext":"Java: list.get(index) throws IndexOutOfBoundsException if out of bounds. Python: list[index] throws IndexError, negative indices wrap. Rust: vec[index] panics, vec.get(index) returns Option. Go: slice[index] panics if out of bounds. Kotlin: list[index] throws, list.getOrElse(index) { default } is safe. EK9: getOrDefault(index, default) always returns a value, no exceptions.","keywords":["access","array","bounds","default","element","exception","getOrDefault","guard","index","list","null-safe","safe"],"primaryTopics":[],"typicalErrors":[{"error":"E50060","correct":"first <- numbers.getOrDefault(0, -1)","incorrect":"first <- numbers.get(0)","explanation":"List has no get(index) method that could throw on invalid indices. Use getOrDefault(index, default) which always returns a usable value. This prevents IndexOutOfBoundsException-style bugs. See ek9 -h E50060 for details."},{"error":"E50060","correct":"third <- numbers.getOrDefault(2, -1)","incorrect":"third <- numbers.elementAt(2)","explanation":"List has no elementAt() method. Use getOrDefault(index, default) which always returns a usable value. See ek9 -h E50060 for details."}],"companions":[]}
{"id":163,"category":"Safe Value Access","question":"How do I safely unwrap an Optional value?","url":"https://ek9.io/qa/QA0163.html","alternatePhrasings":["How do I get the value from an Optional in EK9?","What is the safe way to access an Optional in EK9?","How do I handle empty Optionals in EK9?"],"answer":"EK9 Optionals require a compiler-enforced guard before accessing the contained value. There is no .unwrap() that could crash at runtime. Multiple patterns exist for safe unwrapping.\n\nGUARD WITH IF\nDeclare and check in one expression:\n  if val <- getOptional()\n    extracted <- val.get()\nThe variable only exists inside the if block. If unset, the block is skipped entirely.\n\nEXPLICIT ? CHECK\nCheck with ? then access inside the guarded block:\n  opt <- getOptional()\n  if opt?\n    extracted <- opt.get()\nThe compiler verifies ? was checked before allowing .get().\n\nGETORDEFAULT\nExtract the value with a fallback, no guard needed:\n  name <- opt.getOrDefault(\"default\")\nReturns the contained value if set, otherwise returns the default.\n\nTERNARY GUARD\nCompact single-expression extraction:\n  name <- opt? <- opt.get() else \"default\"\nIf set, evaluates to opt.get(); otherwise uses the default.\n\nSee Q47 for Optional basics. See Q84 for Optional operations. See Q85 for Optional in streams. See Q634 for guard-based safe access. See Q636 for chained guard access.","ek9Example":"defines module qa.safeaccess.optionalsafe\n\n  defines function\n\n    getFilledOptional()\n      <- rtn <- Optional(\"Hello\")\n\n    getEmptyOptional()\n      <- rtn <- Optional() of String\n\n  defines program\n    OptionalUnwrapDemo()\n      stdout <- Stdout()\n\n      // === GUARD WITH IF (DECLARATION FORM) ===\n\n      if val <- getFilledOptional()\n        extracted <- val.get()\n        stdout.println(`Guard unwrap: ${extracted}`)\n\n      // Empty Optional — guard block not entered\n      if val <- getEmptyOptional()\n        stdout.println(\"Should not print\")\n\n      // === EXPLICIT ? CHECK ===\n\n      opt <- getFilledOptional()\n      if opt?\n        checked <- opt.get()\n        stdout.println(`? check unwrap: ${checked}`)\n\n      // === GETORDEFAULT ===\n\n      filled <- getFilledOptional()\n      fromFilled <- filled.getOrDefault(\"fallback\")\n      stdout.println(`GetOrDefault (filled): ${fromFilled}`)\n\n      emptyOpt <- getEmptyOptional()\n      fromEmpty <- emptyOpt.getOrDefault(\"fallback\")\n      stdout.println(`GetOrDefault (empty): ${fromEmpty}`)\n\n      // === TERNARY GUARD ===\n\n      opt2 <- getFilledOptional()\n      ternaryFilled <- opt2? <- opt2.get() else \"default\"\n      stdout.println(`Ternary (filled): ${ternaryFilled}`)\n\n      opt3 <- getEmptyOptional()\n      ternaryEmpty <- opt3? <- opt3.get() else \"default\"\n      stdout.println(`Ternary (empty): ${ternaryEmpty}`)","migrationContext":"Java: Optional.get() throws NoSuchElementException if empty, Optional.orElse() for defaults. Python: no Optional type, None checks are manual. Rust: Option.unwrap() panics if None, unwrap_or() for defaults. Go: no Optional, manual nil checks. Kotlin: nullable T? with !! force-unwrap (throws NPE), ?: elvis for defaults. EK9: compiler-enforced guards, .get() requires ? check, getOrDefault() always safe, no crash paths.","keywords":["absent","access","check","default","get","getOrDefault","guard","isSet","isset","null","null-safe","optional","safe","unwrap"],"primaryTopics":["unwrap optional","get optional value"],"typicalErrors":[{"error":"E08030","correct":"ternaryFilled <- opt2? <- opt2.get() else \"default\"","incorrect":"ternaryFilled <- opt2.get()","explanation":"Calling .get() on an Optional without first checking with ? triggers E08030 — has not been checked before access. The compiler enforces that you verify the Optional is set before extracting its value. See ek9 -h E08030 for details."},{"error":"E50060","correct":"filled.getOrDefault(\"fallback\")","incorrect":"filled.get(\"fallback\")","explanation":"Optional does not have a .get(String) method. Use getOrDefault() for safe extraction with a fallback. Calling a non-existent method triggers E50060 — method not resolved. See ek9 -h E50060 for details."}],"companions":[]}
{"id":164,"category":"Safe Value Access","question":"How do I safely extract the ok or error value from a Result?","url":"https://ek9.io/qa/QA0164.html","alternatePhrasings":["How do I unwrap a Result in EK9?","What is the safe way to access Result ok and error values?","How do I get the success or failure value from a Result in EK9?"],"answer":"EK9 Results require compiler-enforced guards before accessing ok or error values. The isOk() guard unlocks .ok(), and the isError() guard unlocks .error(). Neither unlocks the other.\n\nISOK GUARD THEN OK\nCheck isOk() before accessing the ok value:\n  if r.isOk()\n    okVal <- r.ok()\nThe compiler verifies isOk() was checked before allowing .ok().\n\nISERROR GUARD THEN ERROR\nCheck isError() before accessing the error value:\n  if r.isError()\n    errVal <- r.error()\n\nDUAL GUARD PATTERN\nHandle both sides independently:\n  if r.isOk()\n    process(r.ok())\n  else if r.isError()\n    handleError(r.error())\n\nOKORDEFAULT AND ERRORORDEFAULT\nExtract with a fallback, no guard needed:\n  okVal <- r.okOrDefault(\"fallback\")\n  errVal <- r.errorOrDefault(-1)\nAlways returns a usable value.\n\nTERNARY GUARD\nCompact single-expression extraction:\n  okVal <- r.isOk() <- r.ok() else \"default\"\n  errVal <- r.isError() <- r.error() else 0\n\nSee Q48 for Result basics. See Q86 for Result guard patterns. See Q87 for Result operations and callbacks.","ek9Example":"defines module qa.safeaccess.resultsafe\n\n  defines function\n\n    getOkResult()\n      <- rtn <- Result(\"Success\", Integer())\n\n    getErrorResult()\n      <- rtn <- Result(String(), 42)\n\n  defines program\n    ResultExtractDemo()\n      stdout <- Stdout()\n\n      // === ISOK GUARD THEN OK ===\n\n      r1 <- getOkResult()\n      if r1.isOk()\n        okVal <- r1.ok()\n        stdout.println(`Ok value: ${okVal}`)\n\n      // === ISERROR GUARD THEN ERROR ===\n\n      r2 <- getErrorResult()\n      if r2.isError()\n        errVal <- r2.error()\n        stdout.println(`Error value: ${errVal}`)\n\n      // === DUAL GUARD PATTERN ===\n\n      r3 <- getOkResult()\n      if r3.isOk()\n        stdout.println(`Dual ok: ${r3.ok()}`)\n      else if r3.isError()\n        stdout.println(`Dual error: ${r3.error()}`)\n\n      // === ? OPERATOR (SHORTHAND FOR ISOK) ===\n\n      r4 <- getOkResult()\n      if r4?\n        stdout.println(`? guard ok: ${r4.ok()}`)\n\n      // === OKORDEFAULT / ERRORORDEFAULT ===\n\n      r5 <- getOkResult()\n      safeOk <- r5.okOrDefault(\"fallback\")\n      stdout.println(`okOrDefault (ok result): ${safeOk}`)\n\n      r6 <- getErrorResult()\n      safeOk2 <- r6.okOrDefault(\"fallback\")\n      stdout.println(`okOrDefault (error result): ${safeOk2}`)\n\n      safeErr <- r6.errorOrDefault(-1)\n      stdout.println(`errorOrDefault: ${safeErr}`)\n\n      // === TERNARY GUARD ===\n\n      r7 <- getOkResult()\n      ternaryOk <- r7.isOk() <- r7.ok() else \"default\"\n      stdout.println(`Ternary ok: ${ternaryOk}`)\n\n      r8 <- getErrorResult()\n      ternaryErr <- r8.isError() <- r8.error() else 0\n      stdout.println(`Ternary error: ${ternaryErr}`)","migrationContext":"Java: try-catch for error handling, no Result type, exceptions can be silently ignored. Python: try/except, no compile-time enforcement. Rust: Result.unwrap() panics, pattern matching enforced by compiler. Go: val, err := fn(); if err != nil is convention not enforcement. Kotlin: Result.getOrElse() for defaults, no compile-time guard enforcement. EK9: isOk()/isError() are compiler-enforced guards, okOrDefault()/errorOrDefault() for safe defaults, no escape hatches.","keywords":["access","debug","default","error","extract","guard","isError","isOk","isset","null-safe","ok","okOrDefault","result","safe","unwrap"],"primaryTopics":[],"typicalErrors":[{"error":"E50060","correct":"if r1.isOk()","incorrect":"if r1.isValid()","explanation":"Result does not have an isValid() method. The correct method for checking the success state is isOk(). Calling a non-existent method triggers E50060 — method not resolved. See ek9 -h E50060 for details."}],"companions":[]}
{"id":165,"category":"Safe Value Access","question":"Why don't Dict and List have a .get() method in EK9?","url":"https://ek9.io/qa/QA0165.html","alternatePhrasings":["Why does EK9 force me to use getOrDefault instead of get?","Why is there no .get() on Dict or List in EK9?","What is the design philosophy behind getOrDefault in EK9?"],"answer":"EK9 deliberately omits .get() on Dict and List because it forces a fallback value, eliminating null returns entirely. This is a design philosophy choice, not an oversight.\n\nTHE PROBLEM WITH GET\nIn Java, map.get(key) returns null if the key is missing. This creates silent null propagation: the null travels through your code until it eventually causes a NullPointerException far from the original lookup. In Python, dict[key] throws KeyError. In Go, accessing a missing map key returns the zero value with no indication it was missing.\n\nTHE GETORDEFAULT SOLUTION\nBy requiring a default value, EK9 ensures every lookup returns a meaningful, usable result:\n  ages.getOrDefault(\"Alice\", 0)\nYou must think about the missing-key case at the point of access. The compiler cannot let you forget.\n\nCONSISTENT ACROSS ALL TYPES\nThe same pattern works on Dict, List, Optional, and Result:\n  dict.getOrDefault(key, default)\n  list.getOrDefault(index, default)\n  optional.getOrDefault(default)\n  result.okOrDefault(default)\nOne mental model for safe access everywhere.\n\nSee Q46 for Dict basics. See Q45 for List basics. See Q47 for Optional. See Q48 for Result. See Q166 for the consistent safe-access pattern. See Q168 for fallback values. See Q169 for callbacks.","ek9Example":"defines module qa.safeaccess.designphilosophy\n\n  defines program\n    WhyNoGetDemo()\n      stdout <- Stdout()\n\n      // === THE GETORDEFAULT APPROACH ===\n\n      // Dict: always provide a fallback\n      ages <- {\"Alice\": 30, \"Bob\": 25}\n      aliceAge <- ages.getOrDefault(\"Alice\", 0)\n      stdout.println(`Alice: ${aliceAge}`)\n\n      unknownAge <- ages.getOrDefault(\"Unknown\", 0)\n      stdout.println(`Unknown (default 0): ${unknownAge}`)\n\n      // List: always provide a fallback\n      numbers <- [10, 20, 30]\n      first <- numbers.getOrDefault(0, -1)\n      stdout.println(`First: ${first}`)\n\n      outOfBounds <- numbers.getOrDefault(99, -1)\n      stdout.println(`Out of bounds (default -1): ${outOfBounds}`)\n\n      // Optional: always provide a fallback\n      opt <- Optional(\"Hello\")\n      optVal <- opt.getOrDefault(\"default\")\n      stdout.println(`Optional: ${optVal}`)\n\n      emptyOpt <- Optional() of String\n      emptyVal <- emptyOpt.getOrDefault(\"default\")\n      stdout.println(`Empty optional: ${emptyVal}`)\n\n      // Result: always provide a fallback\n      okResult <- Result(\"Success\", Integer())\n      okVal <- okResult.okOrDefault(\"fallback\")\n      stdout.println(`Result ok: ${okVal}`)\n\n      errResult <- Result(String(), 42)\n      errOk <- errResult.okOrDefault(\"fallback\")\n      stdout.println(`Error result okOrDefault: ${errOk}`)","migrationContext":"Java: map.get() returns null (NPE risk), map.getOrDefault() added in Java 8 but optional. Python: dict[key] throws KeyError, dict.get(key, default) is the safe form. Rust: HashMap.get() returns Option, forces handling. Go: map[key] returns zero value silently, comma-ok idiom for detection. JavaScript: obj[key] returns undefined, no built-in safe access. EK9: no .get() exists, getOrDefault() is the only access method, forces explicit handling of missing keys.","keywords":["access","crash","design","dict","exception","function","get","getOrDefault","guard","list","migrate","missing","null","null-safe","philosophy","safe","why"],"primaryTopics":[],"typicalErrors":[{"error":"E50060","correct":"aliceAge <- ages.getOrDefault(\"Alice\", 0)","incorrect":"aliceAge <- ages.get(\"Alice\")","explanation":"Dict does not have a .get() method — only getOrDefault(). Calling .get() triggers E50060 — method not resolved. This is by design: getOrDefault forces you to handle the missing-key case at the point of access. See ek9 -h E50060 for details."},{"error":"E08030","correct":"optVal <- opt.getOrDefault(\"default\")","incorrect":"optVal <- opt.get()","explanation":"Optional does have .get() but it requires a ? guard check first. Calling .get() without checking triggers E08030 — has not been checked before access. Use getOrDefault() for guard-free access. See ek9 -h E08030 for details."}],"companions":[]}
{"id":166,"category":"Safe Value Access","question":"What is the consistent safe-access pattern across all EK9 types?","url":"https://ek9.io/qa/QA0166.html","alternatePhrasings":["How do I use the same safe access pattern on Dict, List, Optional, and Result?","Is there a unified way to safely access values in EK9?","How does getOrDefault work across different EK9 types?"],"answer":"EK9 provides a consistent safe-access pattern across Dict, List, Optional, and Result. The same mental model applies everywhere: provide a default, get a usable value.\n\nDICT: GETORDEFAULT WITH KEY\n  dict.getOrDefault(key, default)\nLookup by key with fallback.\n\nLIST: GETORDEFAULT WITH INDEX\n  list.getOrDefault(index, default)\nLookup by position with fallback.\n\nOPTIONAL: GETORDEFAULT\n  optional.getOrDefault(default)\nExtract contained value or use fallback.\n\nRESULT: OKORDEFAULT / ERRORORDEFAULT\n  result.okOrDefault(default)\n  result.errorOrDefault(default)\nExtract ok or error value with fallback.\n\nGUARD PATTERN\nAll types support the ? operator for guard checks:\n  if dict contains key then access\n  if opt? then opt.get()\n  if result.isOk() then result.ok()\n\nWHY CONSISTENCY MATTERS\nOne pattern to learn, works everywhere. No need to remember different APIs for different container types. New developers learn the pattern once and apply it across the entire language.\n\nSee Q161 for Dict safe access. See Q162 for List safe access. See Q163 for Optional unwrap. See Q164 for Result extraction. See Q165 for why there is no .get() method.","ek9Example":"defines module qa.safeaccess.consistentpattern\n\n  defines program\n    ConsistentPatternDemo()\n      stdout <- Stdout()\n\n      // === DICT: getOrDefault(key, default) ===\n\n      config <- {\"host\": \"localhost\", \"port\": \"8080\"}\n      host <- config.getOrDefault(\"host\", \"127.0.0.1\")\n      timeout <- config.getOrDefault(\"timeout\", \"30\")\n      stdout.println(`Host: ${host}`)\n      stdout.println(`Timeout (default): ${timeout}`)\n\n      // === LIST: getOrDefault(index, default) ===\n\n      args <- [\"run\", \"test\", \"verbose\"]\n      command <- args.getOrDefault(0, \"help\")\n      missing <- args.getOrDefault(10, \"none\")\n      stdout.println(`Command: ${command}`)\n      stdout.println(`Missing arg: ${missing}`)\n\n      // === OPTIONAL: getOrDefault(default) ===\n\n      userName <- Optional(\"Steve\")\n      displayName <- userName.getOrDefault(\"Anonymous\")\n      stdout.println(`User: ${displayName}`)\n\n      noUser <- Optional() of String\n      fallbackName <- noUser.getOrDefault(\"Anonymous\")\n      stdout.println(`No user: ${fallbackName}`)\n\n      // === RESULT: okOrDefault(default) ===\n\n      okResult <- Result(\"Data loaded\", Integer())\n      msg <- okResult.okOrDefault(\"No data\")\n      stdout.println(`Result msg: ${msg}`)\n\n      errResult <- Result(String(), 404)\n      errMsg <- errResult.okOrDefault(\"No data\")\n      stdout.println(`Error result msg: ${errMsg}`)\n\n      errCode <- errResult.errorOrDefault(0)\n      stdout.println(`Error code: ${errCode}`)","migrationContext":"Java: different APIs per type (Map.get, List.get, Optional.orElse, no Result). Python: dict.get(), list[i] (throws), no Optional. Rust: HashMap.get() returns Option, Vec.get() returns Option, unified Option/Result but different APIs. Go: comma-ok for maps, panic for slices, no Optional/Result. Kotlin: different null-safe operators per context. EK9: getOrDefault() everywhere, one pattern for all container types.","keywords":["absent","access","consistent","dict","error","getOrDefault","guard","isset","list","null-safe","ok","optional","pattern","result","safe","unified"],"primaryTopics":["safe access pattern","null safety"],"typicalErrors":[{"error":"E50060","correct":"host <- config.getOrDefault(\"host\", \"127.0.0.1\")","incorrect":"host <- config.get(\"host\")","explanation":"Dict does not have a .get() method. Use getOrDefault() with a fallback value. Calling a non-existent method triggers E50060 — method not resolved. See ek9 -h E50060 for details."},{"error":"E08030","correct":"displayName <- userName.getOrDefault(\"Anonymous\")","incorrect":"displayName <- userName.get()","explanation":"Optional.get() requires a ? guard check first. Calling .get() without checking triggers E08030 — has not been checked before access. Use getOrDefault() for guard-free access. See ek9 -h E08030 for details."}],"companions":[]}
{"id":167,"category":"Safe Value Access","question":"How do I iterate over Dict entries safely without worrying about missing keys?","url":"https://ek9.io/qa/QA0167.html","alternatePhrasings":["How do I loop over all key-value pairs in an EK9 Dict?","How do I use for-in with a Dict in EK9?","How do I iterate over Dict keys and values separately in EK9?"],"answer":"EK9 Dicts provide three iteration approaches: for-in over entries, .keys() iterator, and .values() iterator. All are safe because every yielded item is guaranteed to be set.\n\nFOR-IN OVER ENTRIES\nThe simplest and most common pattern:\n  for entry in dict\n    stdout.println(`${entry.key()}: ${entry.value()}`)\nEach entry is a DictEntry with .key() and .value() accessors.\n\nKEYS ITERATOR\nIterate over just the keys:\n  keyIter <- dict.keys()\n  while keyIter?\n    stdout.println(keyIter.next())\n\nVALUES ITERATOR\nIterate over just the values:\n  valIter <- dict.values()\n  while valIter?\n    stdout.println($valIter.next())\n\nSTREAM PIPELINE\nUse cat with a Dict for stream processing:\n  cat dict | map with entryToString | collect as List of String\nStreams over DictEntry items.\n\nSee Q46 for Dict basics. See Q90 for Dict operations including keys/values. See Q89 for stream pipelines.\n\nSee Q46 for dict. See Q166 for consistent safe pattern.","ek9Example":"defines module qa.safeaccess.dictiteration\n\n  defines function\n\n    entryToString() as pure\n      -> entry as DictEntry of (String, Integer)\n      <- rtn as String: `${entry.key()} = ${entry.value()}`\n\n  defines program\n    DictIterationDemo()\n      stdout <- Stdout()\n\n      scores <- {\"Alice\": 95, \"Bob\": 87, \"Charlie\": 92}\n\n      // === FOR-IN OVER ENTRIES ===\n\n      stdout.println(\"All entries:\")\n      for entry in scores\n        stdout.println(`  ${entry.key()}: ${entry.value()}`)\n\n      // === KEYS ITERATOR ===\n\n      stdout.println(\"Keys:\")\n      keyIter <- scores.keys()\n      while keyIter?\n        stdout.println(`  ${keyIter.next()}`)\n\n      // === VALUES ITERATOR ===\n\n      stdout.println(\"Values:\")\n      valIter <- scores.values()\n      while valIter?\n        stdout.println(`  ${valIter.next()}`)\n\n      // === STREAM PIPELINE ===\n\n      formatted <- cat scores | map with entryToString | collect as List of String\n      stdout.println(`Formatted: ${formatted}`)\n\n      // === SAFE: EMPTY DICT ITERATION ===\n\n      emptyDict <- Dict() of (String, Integer)\n      for entry in emptyDict\n        stdout.println(\"Should not print\")\n      stdout.println(\"Empty dict iteration completed safely\")","migrationContext":"Java: map.entrySet().forEach(), map.keySet(), map.values(). Python: for k, v in dict.items(), dict.keys(), dict.values(). Rust: for (k, v) in map.iter(). Go: for k, v := range m. JavaScript: Object.entries(), Object.keys(), Object.values(). EK9: for entry in dict yields DictEntry, .keys()/.values() return iterators, cat dict for stream pipelines.","keywords":["DictEntry","access","dict","entries","for","guard","iterate","keys","loop","null-safe","safe","stream","values"],"primaryTopics":[],"typicalErrors":[{"error":"E06020","correct":"-> entry as DictEntry of (String, Integer)","incorrect":"-> entry as DictEntry of String","explanation":"DictEntry requires two type parameters (key and value types). Providing only one triggers E06020 — incorrect number of parameters supplied. Use DictEntry of (KeyType, ValueType). See ek9 -h E06020 for details."}],"companions":[]}
{"id":168,"category":"Safe Value Access","question":"How do I provide a fallback value when something might be unset?","url":"https://ek9.io/qa/QA0168.html","alternatePhrasings":["How do I set a default value for an unset variable in EK9?","What is the guarded assignment operator in EK9?","How do I chain fallback values in EK9?"],"answer":"EK9 provides several mechanisms for fallback values: the :=? guarded assignment, getOrDefault(), and the ternary guard pattern.\n\nGUARDED ASSIGNMENT (:=?)\nOnly assigns if the target is currently unset:\n  name <- String()\n  name :=? \"default\"\nIf name was unset, it becomes \"default\". If already set, unchanged. This is ideal for layered defaults.\n\nLAYERED DEFAULTS\nApply multiple fallback layers:\n  setting <- String()\n  setting :=? userPreference\n  setting :=? systemDefault\n  setting :=? \"hardcoded\"\nEach :=? only applies if the variable is still unset.\n\nGETORDEFAULT\nExtract from containers with a fallback:\n  dict.getOrDefault(key, default)\n  list.getOrDefault(index, default)\n  optional.getOrDefault(default)\n\nTERNARY GUARD\nSingle-expression fallback:\n  name <- opt? <- opt.get() else \"default\"\n\nGUARD IN IF\nConditional processing with fallback in else:\n  if val <- getOptional()\n    stdout.println(val.get())\n  else\n    stdout.println(\"No value available\")\n\nSee Q22 for variable declaration. See Q29 for tri-state semantics. See Q79 for guarded assignment. See Q84 for Optional ternary guard.\n\nSee Q29 for unset variables. See Q166 for consistent safe pattern. See Q243 for coalescing operators.","ek9Example":"defines module qa.safeaccess.fallback\n\n  defines program\n    FallbackValuesDemo()\n      stdout <- Stdout()\n\n      // === GUARDED ASSIGNMENT (:=?) ===\n\n      name <- String()\n      stdout.println(`Before guard: isSet=${name?}`)\n\n      name :=? \"default\"\n      stdout.println(`After first guard: ${name}`)\n\n      name :=? \"other\"\n      stdout.println(`After second guard (unchanged): ${name}`)\n\n      // === LAYERED DEFAULTS ===\n\n      // Simulate layered configuration\n      userPref <- String()\n      systemDefault <- String()\n      hardcoded <- \"en-US\"\n\n      locale <- String()\n      locale :=? userPref\n      locale :=? systemDefault\n      locale :=? hardcoded\n      stdout.println(`Locale (from hardcoded): ${locale}`)\n\n      // Now with user preference set\n      locale2 <- String()\n      userPref2 <- \"fr-FR\"\n      locale2 :=? userPref2\n      locale2 :=? \"en-US\"\n      stdout.println(`Locale (from user): ${locale2}`)\n\n      // === GETORDEFAULT ON CONTAINERS ===\n\n      config <- {\"debug\": \"true\"}\n      debugMode <- config.getOrDefault(\"debug\", \"false\")\n      verboseMode <- config.getOrDefault(\"verbose\", \"false\")\n      stdout.println(`Debug: ${debugMode}`)\n      stdout.println(`Verbose: ${verboseMode}`)\n\n      items <- [100, 200, 300]\n      firstItem <- items.getOrDefault(0, -1)\n      missingItem <- items.getOrDefault(99, -1)\n      stdout.println(`First: ${firstItem}`)\n      stdout.println(`Missing: ${missingItem}`)\n\n      // === TERNARY GUARD ===\n\n      opt <- Optional(\"Hello\")\n      greeting <- opt? <- opt.get() else \"Hi\"\n      stdout.println(`Greeting: ${greeting}`)\n\n      emptyOpt <- Optional() of String\n      fallbackGreeting <- emptyOpt? <- emptyOpt.get() else \"Hi\"\n      stdout.println(`Fallback greeting: ${fallbackGreeting}`)","migrationContext":"Java: ternary operator (x != null ? x : default), Optional.orElse(), no guarded assignment. Python: x = x or default (falsy gotcha), x if x is not None else default. Rust: unwrap_or(), unwrap_or_else() for lazy defaults. Go: if x == nil { x = default }. Kotlin: ?: elvis operator for null defaults. Swift: ?? nil coalescing for optional defaults, if let with else for guarded fallback. JavaScript: ?? nullish coalescing, || logical or. EK9: :=? guarded assignment for layered defaults, getOrDefault() on containers, ternary guard pattern.","keywords":["access","assignment","chain","default","fallback","getOrDefault","guarded","isset","layered","null-safe","priority","safe","swift","ternary","unset"],"primaryTopics":[],"typicalErrors":[{"error":"E50030","correct":"name :=? \"default\"","incorrect":"name :=? 42","explanation":"Guarded assignment :=? requires the value type to match the variable type. Assigning an Integer to a String variable triggers E50030 — types are not compatible. Ensure the fallback value matches the declared type. See ek9 -h E50030 for details."}],"companions":[]}
{"id":169,"category":"Safe Value Access","question":"How do I process an Optional or Result value using callbacks?","url":"https://ek9.io/qa/QA0169.html","alternatePhrasings":["How do whenPresent and whenOk work in EK9?","How do I use callbacks with Optional and Result in EK9?","What is the reactive pattern for Optional and Result in EK9?"],"answer":"EK9 Optional and Result support callback-based processing using whenPresent, whenOk, and whenError. These accept Consumer (pure, read-only) or Acceptor (non-pure, can mutate) functions.\n\nOPTIONAL WHENPRESENT\nProcess the contained value only if present:\n  optional.whenPresent(myConsumer)\nThe callback is never invoked if the Optional is empty. Safe by design.\n\nRESULT WHENOK AND WHENERROR\nProcess ok or error values independently:\n  result.whenOk(okConsumer)\n  result.whenError(errorConsumer)\nEach callback fires only if its corresponding value is present.\n\nCONSUMER VS ACCEPTOR\nConsumer is pure (read-only, no side effects). Acceptor can perform I/O or mutation:\n  optional.whenPresent(pureProcessor)      Consumer\n  result.whenOk(sideEffectProcessor)       Acceptor (when non-pure)\n\nWHEN TO USE CALLBACKS VS GUARDS\nCallbacks are cleaner when you just need to process the value. Guards are better when you need the value for further computation.\n\nSee Q54 for Consumer and Acceptor function types. See Q84 for Optional operations. See Q87 for Result operations.\n\nSee Q52 for dynamic functions. See Q51 for abstract functions.","ek9Example":"defines module qa.safeaccess.callbacks\n\n  defines function\n\n    logString() as pure\n      -> content as String\n      require content?\n\n    logInteger() as pure\n      -> code as Integer\n      require code?\n\n  defines program\n    CallbacksDemo()\n      stdout <- Stdout()\n\n      // === OPTIONAL WHENPRESENT ===\n\n      filled <- Optional(\"Hello\")\n      filled.whenPresent(logString)\n      stdout.println(\"whenPresent on filled Optional: callback fired\")\n\n      emptyOpt <- Optional() of String\n      emptyOpt.whenPresent(logString)\n      stdout.println(\"whenPresent on empty Optional: callback NOT fired\")\n\n      // === RESULT WHENOK ===\n\n      okResult <- Result(\"Success\", Integer())\n      okResult.whenOk(logString)\n      stdout.println(\"whenOk on ok Result: callback fired\")\n\n      errResult <- Result(String(), 42)\n      errResult.whenOk(logString)\n      stdout.println(\"whenOk on error Result: callback NOT fired\")\n\n      // === RESULT WHENERROR ===\n\n      errResult.whenError(logInteger)\n      stdout.println(\"whenError on error Result: callback fired\")\n\n      okResult.whenError(logInteger)\n      stdout.println(\"whenError on ok Result: callback NOT fired\")\n\n      // === BOTH CALLBACKS ON DUAL-VALUE RESULT ===\n\n      bothResult <- Result(\"Partial\", -1)\n      bothResult.whenOk(logString)\n      bothResult.whenError(logInteger)\n      stdout.println(\"Both callbacks on dual-value Result\")\n\n      // === EMPTY RESULT — NEITHER FIRES ===\n\n      emptyResult <- Result() of (String, Integer)\n      emptyResult.whenOk(logString)\n      emptyResult.whenError(logInteger)\n      stdout.println(\"Empty Result: neither callback fires\")","migrationContext":"Java: Optional.ifPresent(consumer), no Result type. Python: no callbacks on Optional (no Optional type). Rust: Option.map(), Result.map()/map_err() for transformation, no direct callback. Go: no Optional/Result, manual if checks. Kotlin: let/also/run scope functions, Result.onSuccess()/onFailure(). EK9: whenPresent for Optional, whenOk/whenError for Result, Consumer (pure) vs Acceptor (non-pure).","keywords":["absent","acceptor","access","callback","consumer","error","guard","null-safe","ok","optional","process","reactive","result","safe","whenError","whenOk","whenPresent"],"primaryTopics":[],"typicalErrors":[{"error":"E50060","correct":"filled.whenPresent(logString)","incorrect":"filled.ifPresent(logString)","explanation":"Optional does not have an ifPresent() method. The correct method name is whenPresent(). Calling a non-existent method triggers E50060 — method not resolved. See ek9 -h E50060 for details."}],"companions":[]}
{"id":170,"category":"Common String Operations","question":"How do I get the length of a string in EK9?","url":"https://ek9.io/qa/QA0170.html","alternatePhrasings":["How do I count the characters in a string in EK9?","What is the EK9 equivalent of string.length()?","How do I check if a string is empty in EK9?"],"answer":"EK9 provides two ways to get string length and a dedicated empty check.\n\nLENGTH OPERATOR\nPrefix operator syntax:\n  len <- length myString\nReturns the number of characters.\n\nMETHOD SYNTAX\n  len <- myString.length()\nEquivalent to the operator form.\n\nEMPTY CHECK\nTest whether a string has no characters:\n  if myString is empty\n    stdout.println(\"No content\")\nNote: an empty string (length 0) IS set. An unset string is different from an empty string.\n\nSee Q37 for comprehensive string operations. See Q29 for the distinction between empty and unset. See Q242 for conversion and introspection operators including length.","ek9Example":"defines module qa.stringops.length\n\n  defines program\n    StringLengthDemo()\n      stdout <- Stdout()\n\n      greeting <- \"Hello World\"\n\n      // === LENGTH OPERATOR ===\n\n      len1 <- length greeting\n      stdout.println(`Length (operator): ${len1}`)\n\n      // === METHOD SYNTAX ===\n\n      len2 <- greeting.length()\n      stdout.println(`Length (method): ${len2}`)\n\n      // === EMPTY CHECK ===\n\n      emptyStr <- \"\"\n      stdout.println(`Empty string length: ${length emptyStr}`)\n      stdout.println(`Is empty: ${emptyStr is empty}`)\n\n      // Empty string IS set (not unset)\n      require emptyStr?\n      stdout.println(`Empty string isSet: ${emptyStr?}`)\n\n      // Non-empty check\n      if ~greeting is empty\n        stdout.println(\"Greeting has content\")\n\n      // === UNSET STRING ===\n\n      unsetStr <- String()\n      stdout.println(`Unset string isSet: ${unsetStr?}`)","migrationContext":"Java: str.length(), str.isEmpty(). Python: len(str), not str for empty check. Rust: str.len(), str.is_empty(). Go: len(str). JavaScript: str.length property. Kotlin: str.length, str.isEmpty(). EK9: length str or str.length(), str is empty.","keywords":["characters","count","empty","length","size","string","text"],"primaryTopics":["string length","length of string"],"typicalErrors":[{"error":"E50060","correct":"len1 <- length greeting","incorrect":"len1 <- greeting.size()","explanation":"String has no size() method. Use the length prefix operator or .length() method. See ek9 -h E50060 for details."}],"companions":[]}
{"id":171,"category":"Common String Operations","question":"How do I check if a string contains a substring in EK9?","url":"https://ek9.io/qa/QA0171.html","alternatePhrasings":["How do I search for text within a string in EK9?","What is the EK9 equivalent of string.contains()?","How do I check if a string includes a substring in EK9?"],"answer":"EK9 uses the contains operator for substring checks and matches for regex pattern matching.\n\nCONTAINS OPERATOR\nCheck if a string contains a substring:\n  if sentence contains \"fox\"\n    stdout.println(\"Found fox\")\nReturns Boolean. Case-sensitive.\n\nIS IN / IS NOT IN\nNatural language syntax:\n  if \"fox\" is in sentence\n    stdout.println(\"Found\")\n  if \"wolf\" is not in sentence\n    stdout.println(\"Not found\")\n\nMATCHES (REGEX)\nFor pattern-based searching:\n  if sentence matches /[Ff]ox/\n    stdout.println(\"Matches fox pattern\")\n\nSee Q37 for string basics. See Q33 for regular expressions.","ek9Example":"defines module qa.stringops.contains\n\n  defines program\n    StringContainsDemo()\n      stdout <- Stdout()\n\n      sentence <- \"The Quick Brown Fox Jumps\"\n\n      // === CONTAINS OPERATOR ===\n\n      stdout.println(`Contains 'Brown': ${sentence contains \"Brown\"}`)\n      stdout.println(`Contains 'wolf': ${sentence contains \"wolf\"}`)\n\n      // === IS IN / IS NOT IN ===\n\n      if \"Quick\" is in sentence\n        stdout.println(\"Found Quick\")\n\n      if \"wolf\" is not in sentence\n        stdout.println(\"wolf not found\")\n\n      // === MATCHES (REGEX) ===\n\n      if sentence matches /.*Fox.*/\n        stdout.println(\"Matches Fox pattern\")\n\n      if sentence matches /.*[Bb]rown.*/\n        stdout.println(\"Matches Brown pattern (case flexible)\")\n\n      // Case-sensitive contains\n      stdout.println(`Contains 'quick' (lowercase): ${sentence contains \"quick\"}`)\n      stdout.println(`Contains 'Quick' (correct case): ${sentence contains \"Quick\"}`)","migrationContext":"Java: str.contains(sub), str.matches(regex). Python: sub in str, re.search(). Rust: str.contains(sub). Go: strings.Contains(str, sub). JavaScript: str.includes(sub), str.match(regex). Kotlin: sub in str, str.contains(sub). EK9: str contains sub, sub is in str, str matches /pattern/.","keywords":["check","contains","find","includes","indexOf","match","search","string","substring","text"],"primaryTopics":["string contains","substring check"],"typicalErrors":[{"error":"E50060","correct":"sentence contains \"Brown\"","incorrect":"sentence.indexOf(\"Brown\") >= 0","explanation":"String has no indexOf() method. Use the contains operator for substring checks. See ek9 -h E50060 for details."}],"companions":[]}
{"id":172,"category":"Common String Operations","question":"How do I convert a string to uppercase or lowercase in EK9?","url":"https://ek9.io/qa/QA0172.html","alternatePhrasings":["What is the EK9 equivalent of toUpperCase()?","How do I change string case in EK9?","How do I make a string all caps or all lowercase in EK9?"],"answer":"EK9 strings have .upperCase() and .lowerCase() methods that return new strings.\n\nUPPERCASE\nConvert to all uppercase letters:\n  upper <- myString.upperCase()\nReturns a new string. The original is unchanged.\n\nLOWERCASE\nConvert to all lowercase letters:\n  lower <- myString.lowerCase()\n\nCASE-INSENSITIVE COMPARISON\nConvert both strings to the same case before comparing:\n  if input.lowerCase() == \"yes\"\n    stdout.println(\"Confirmed\")\n\nSee Q37 for comprehensive string operations.\n\nSee Q37 for strings. See Q170 for string length.","ek9Example":"defines module qa.stringops.caseconversion\n\n  defines program\n    StringCaseDemo()\n      stdout <- Stdout()\n\n      mixed <- \"Hello World\"\n\n      // === UPPERCASE ===\n\n      upper <- mixed.upperCase()\n      stdout.println(`Upper: ${upper}`)\n\n      // === LOWERCASE ===\n\n      lower <- mixed.lowerCase()\n      stdout.println(`Lower: ${lower}`)\n\n      // Original unchanged\n      stdout.println(`Original: ${mixed}`)\n\n      // === CASE-INSENSITIVE COMPARISON ===\n\n      input <- \"YES\"\n      if input.lowerCase() == \"yes\"\n        stdout.println(\"Confirmed (case-insensitive)\")\n\n      userInput <- \"hello\"\n      if userInput.upperCase() == \"HELLO\"\n        stdout.println(\"Match (case-insensitive)\")\n\n      // === PRACTICAL: NORMALIZE BEFORE LOOKUP ===\n\n      config <- {\"debug\": \"true\", \"verbose\": \"false\"}\n      key <- \"DEBUG\"\n      normalized <- config.getOrDefault(key.lowerCase(), \"false\")\n      stdout.println(`Config ${key}: ${normalized}`)","migrationContext":"Java: str.toUpperCase(), str.toLowerCase(). Python: str.upper(), str.lower(). Rust: str.to_uppercase(), str.to_lowercase(). Go: strings.ToUpper(), strings.ToLower(). JavaScript: str.toUpperCase(), str.toLowerCase(). Kotlin: str.uppercase(), str.lowercase(). EK9: str.upperCase(), str.lowerCase().","keywords":["case","convert","lower","lowercase","string","text","toLowerCase","toUpperCase","upper","uppercase"],"primaryTopics":["uppercase","lowercase","string case"],"typicalErrors":[{"error":"E50060","correct":"upper <- mixed.upperCase()","incorrect":"upper <- mixed.toUpperCase()","explanation":"String has no toUpperCase() method. The correct method name in EK9 is upperCase(). See ek9 -h E50060 for details."},{"error":"E11068","correct":"stdout.println(`Config ${key}: ${normalized}`)","incorrect":"stdout.println(\"Config \" + key + \": \" + normalized)","explanation":"Concatenating 3 or more string parts with + triggers E11068. Use backtick interpolation instead. See ek9 -h E11068 for details."},{"error":"E50060","correct":"upper <- mixed.upperCase()","incorrect":"upper <- mixed.toUpper()","explanation":"String has no toUpper() method. The correct EK9 method is upperCase(). Use 'ek9 -h String' to see the full API. See ek9 -h E50060 for details."}],"companions":[]}
{"id":173,"category":"Common String Operations","question":"How do I trim whitespace from a string in EK9?","url":"https://ek9.io/qa/QA0173.html","alternatePhrasings":["How do I remove leading and trailing spaces from a string in EK9?","What is the EK9 equivalent of string.trim()?","How do I strip whitespace from a string in EK9?"],"answer":"EK9 strings have .trim() for whitespace removal and .trim(char) for removing a specific character.\n\nTRIM WHITESPACE\nRemove leading and trailing whitespace:\n  cleaned <- myString.trim()\nReturns a new string with spaces, tabs, and newlines removed from both ends.\n\nTRIM SPECIFIC CHARACTER\nRemove a specific character from both ends:\n  unquoted <- myString.trim('\"')\nUseful for removing surrounding quotes or delimiters.\n\nCOMBINE WITH PADDING\nTrim is the complement of padding:\n  padded <- myString.rightPadded(30)\n  trimmed <- padded.trim()\n\nSee Q37 for comprehensive string operations. See Q176 for string padding. See Q172 for string case. See Q174 for string concat. See Q175 for string transform.","ek9Example":"defines module qa.stringops.trim\n\n  defines program\n    StringTrimDemo()\n      stdout <- Stdout()\n\n      // === TRIM WHITESPACE ===\n\n      padded <- \"  Hello World  \"\n      trimmed <- padded.trim()\n      stdout.println(`Before: [${padded}]`)\n      stdout.println(`After trim: [${trimmed}]`)\n\n      // === TRIM SPECIFIC CHARACTER ===\n\n      quoted <- \"\\\"Hello World\\\"\"\n      unquoted <- quoted.trim('\"')\n      stdout.println(`Quoted: ${quoted}`)\n      stdout.println(`Unquoted: ${unquoted}`)\n\n      dashed <- \"---Title---\"\n      undashed <- dashed.trim('-')\n      stdout.println(`Dashed: ${dashed}`)\n      stdout.println(`Undashed: ${undashed}`)\n\n      // === COMPLEMENT OF PADDING ===\n\n      original <- \"Test\"\n      rightPad <- original.rightPadded(20)\n      stdout.println(`Right padded: [${rightPad}]`)\n\n      backToOriginal <- rightPad.trim()\n      stdout.println(`Trimmed back: [${backToOriginal}]`)","migrationContext":"Java: str.trim(), str.strip() (Java 11+). Python: str.strip(), str.strip(chars). Rust: str.trim(), str.trim_matches(char). Go: strings.TrimSpace(), strings.Trim(). JavaScript: str.trim(). Kotlin: str.trim(), str.trim(char). EK9: str.trim(), str.trim(char).","keywords":["clean","leading","spaces","string","strip","text","trailing","trim","whitespace"],"primaryTopics":["trim","strip whitespace"],"typicalErrors":[{"error":"E50060","correct":"trimmed <- padded.trim()","incorrect":"trimmed <- padded.strip()","explanation":"String has no strip() method. The correct method name in EK9 is trim(). See ek9 -h E50060 for details."},{"error":"E50060","correct":"trimmed <- padded.trim()","incorrect":"trimmed <- padded.trimLeft()","explanation":"String has no trimLeft() method. EK9 provides trim() for both ends and trim(char) for a specific character. Use 'ek9 -h String' to see the full API. See ek9 -h E50060 for details."}],"companions":[]}
{"id":174,"category":"Common String Operations","question":"How do I join or concatenate strings in EK9?","url":"https://ek9.io/qa/QA0174.html","alternatePhrasings":["How do I combine strings together in EK9?","What is the EK9 equivalent of string concatenation?","How do I embed expressions inside EK9 strings?"],"answer":"EK9 provides several ways to combine strings: backtick interpolation (preferred), the + operator (for simple cases), += append, and stream collect.\n\nBACKTICK INTERPOLATION (preferred)\nEmbed expressions in backtick strings:\n  stdout.println(`Welcome ${name}, you are ${age} years old`)\nAny expression inside ${...} is evaluated and converted to string. Interpolation compiles to a single allocation via a bespoke STRING_INTERPOLATION IR instruction. Always prefer this for 3+ parts.\n\nPLUS OPERATOR (2-part only)\nConcatenate two strings with +:\n  full <- first + second\nCreates a new string from two parts. Chains of 3 or more parts (e.g. a + b + c) trigger compiler error E11068 because each + creates an intermediate String object. Use interpolation instead:\n  BAD:  result <- first + \" \" + second\n  GOOD: result <- `${first} ${second}`\n\nAPPEND (+=)\nMutate a string variable by appending:\n  greeting <- \"Hello\"\n  greeting += \" World\"\n\nSTREAM COLLECT\nJoin a list of strings via stream pipeline:\n  combined <- cat [\"Hello\", \" \", \"World\"] | collect as String\n\nSee Q37 for string basics. See Q43 for escape sequences in interpolation. See Q309 for the E11068 compiler error details.","ek9Example":"defines module qa.stringops.concat\n\n  defines program\n    StringConcatDemo()\n      stdout <- Stdout()\n\n      first <- \"Hello\"\n      second <- \"World\"\n\n      // === BACKTICK INTERPOLATION (preferred) ===\n\n      // Interpolation compiles to a single allocation\n      combined <- `${first} ${second}`\n      stdout.println(`Interpolated: ${combined}`)\n\n      name <- \"Steve\"\n      age <- 30\n      stdout.println(`Name: ${name}, Age: ${age}`)\n\n      // Expressions inside interpolation\n      stdout.println(`Upper: ${name.upperCase()}`)\n      stdout.println(`Length: ${length name}`)\n\n      // === PLUS OPERATOR (2-part only) ===\n\n      // Two-part concatenation is allowed\n      twoPartConcat <- first + second\n      stdout.println(`Two-part: ${twoPartConcat}`)\n\n      // Three or more parts would trigger E11068:\n      //   result <- first + \" \" + second   // Error!\n      // Use interpolation instead:\n      //   result <- `${first} ${second}`    // Correct\n\n      // === APPEND (+=) ===\n\n      greeting <- \"Hello\"\n      greeting += \" \"\n      greeting += \"World\"\n      stdout.println(`Appended: ${greeting}`)\n\n      // === STREAM COLLECT ===\n\n      parts <- [\"Hello\", \" \", \"World\", \"!\"]\n      joined <- cat parts | collect as String\n      stdout.println(`Stream joined: ${joined}`)\n\n      // === BUILDING FROM MULTIPLE VALUES ===\n\n      host <- \"localhost\"\n      port <- 8080\n      url <- `http://${host}:${port}`\n      stdout.println(`URL: ${url}`)\n\n      // Interpolation is cleaner for mixed types\n      stdout.println(`URL: http://${host}:${port}`)","migrationContext":"Java: + operator, String.concat(), StringBuilder, String.format(). Python: + operator, f-strings, .join(). Rust: format!() macro, + operator, .push_str(). Go: + operator, fmt.Sprintf(). JavaScript: + operator, template literals. Kotlin: + operator, string templates. EK9: backtick interpolation (preferred), + operator (2-part only, 3+ is E11068), += append, stream collect.","keywords":["E11068","append","combine","concat","concatenate","interpolation","join","merge","plus","string","text"],"primaryTopics":["concatenate string","join strings"],"typicalErrors":[{"error":"E11068","correct":"combined <- `${first} ${second}`","incorrect":"combined <- first + \" \" + second","explanation":"Concatenating 3 or more string parts with + triggers E11068. Use backtick interpolation instead. See ek9 -h E11068 for details."}],"companions":[]}
{"id":175,"category":"Common String Operations","question":"How do I replace or transform text in a string in EK9?","url":"https://ek9.io/qa/QA0175.html","alternatePhrasings":["Does EK9 have a string replace method?","How do I substitute text in a string in EK9?","How do I transform characters in a string in EK9?"],"answer":"EK9 strings do not have a .replace() method. Instead, use split + stream rejoin for pattern-based replacement, or stream characters with map/filter for character-level transformations.\n\nSPLIT AND REJOIN\nBreak at a pattern and collect back together:\n  parts <- sentence.split(/,/)\nSplit returns a List of String. Rejoin the parts using stream collect:\n  rejoined <- cat parts | collect as String\n\nCHARACTER STREAMING\nStrings are streamable as character sequences. Transform each character:\n  cat myString | map with toUpper | collect as String\nFilter out specific characters:\n  cat myString | filter by isNotSpace | collect as String\n\nWHY NO REPLACE\nEK9's philosophy is that strings are streamable sequences. Rather than adding dozens of specialized methods, EK9 leverages the universal stream pipeline for transformations.\n\nSee Q37 for string streaming. See Q33 for regular expressions. See Q89 for stream pipelines.","ek9Example":"defines module qa.stringops.transform\n\n  defines function\n\n    isNotComma() as pure\n      -> ch as Character\n      <- rtn as Boolean: ch <> ','\n\n    charToUpper() as pure\n      -> ch as Character\n      <- rtn as String: $ch.upperCase()\n\n    isLetter() as pure\n      -> ch as Character\n      <- rtn as Boolean: $ch matches /[A-Za-z]/\n\n  defines program\n    StringTransformDemo()\n      stdout <- Stdout()\n\n      // === SPLIT AND REJOIN ===\n\n      csv <- \"Alice,Bob,Charlie\"\n      parts <- csv.split(/,/)\n      stdout.println(`Split: ${parts}`)\n\n      // Rejoin without commas\n      rejoined <- cat parts | collect as String\n      stdout.println(`Rejoined: ${rejoined}`)\n\n      // === CHARACTER STREAMING — FILTER ===\n\n      // Remove commas by filtering characters\n      noCommas <- cat csv | filter by isNotComma | collect as String\n      stdout.println(`No commas: ${noCommas}`)\n\n      // === CHARACTER STREAMING — MAP ===\n\n      // Transform to uppercase via character stream\n      lower <- \"hello world\"\n      upper <- cat lower | map with charToUpper | collect as String\n      stdout.println(`Uppercased via stream: ${upper}`)\n\n      // === EXTRACT ONLY LETTERS ===\n\n      messy <- \"h3ll0 w0rld!\"\n      lettersOnly <- cat messy | filter by isLetter | collect as String\n      stdout.println(`Letters only: ${lettersOnly}`)\n\n      // === SPLIT WITH REGEX ===\n\n      sentence <- \"one  two   three\"\n      words <- sentence.split(/ +/)\n      stdout.println(`Words: ${words}`)","migrationContext":"Java: str.replace(), str.replaceAll(regex). Python: str.replace(), re.sub(). Rust: str.replace(), str.replacen(). Go: strings.Replace(), strings.ReplaceAll(). JavaScript: str.replace(), str.replaceAll(). Kotlin: str.replace(), str.replace(regex). EK9: no .replace(), use split(regex) + collect, or stream characters with map/filter.","keywords":["characters","filter","map","regex","replace","split","stream","string","substitute","text","transform"],"primaryTopics":[],"typicalErrors":[{"error":"E50060","correct":"parts <- csv.split(/,/)","incorrect":"parts <- csv.replace(\",\", \" \")","explanation":"String has no replace() method. Use split + stream collect, or stream characters with filter/map for transformations. See ek9 -h E50060 for details."}],"companions":[]}
{"id":176,"category":"Common String Operations","question":"How do I pad a string to a fixed width in EK9?","url":"https://ek9.io/qa/QA0176.html","alternatePhrasings":["How do I align text to a fixed column width in EK9?","What is the EK9 equivalent of string padding?","How do I right-align or left-align text in EK9?"],"answer":"EK9 strings have .leftPadded(n) and .rightPadded(n) methods for padding to a fixed width.\n\nRIGHT PADDED\nPad with spaces on the right to reach width n:\n  padded <- myString.rightPadded(20)\nUseful for left-aligned columns. If the string is already longer than n, it is returned unchanged.\n\nLEFT PADDED\nPad with spaces on the left to reach width n:\n  padded <- myString.leftPadded(20)\nUseful for right-aligned columns.\n\nFORMATTING COLUMNS\nCombine padding for tabular output:\n  stdout.println(`${name.rightPadded(15)} ${score.leftPadded(5)}`)\n\nSee Q37 for comprehensive string operations. See Q173 for trim (the complement of padding).","ek9Example":"defines module qa.stringops.padding\n\n  defines program\n    StringPaddingDemo()\n      stdout <- Stdout()\n\n      // === RIGHT PADDED (left-aligned) ===\n\n      word <- \"Hello\"\n      rightPad <- word.rightPadded(20)\n      stdout.println(`Right padded: [${rightPad}]`)\n\n      // === LEFT PADDED (right-aligned) ===\n\n      leftPad <- word.leftPadded(20)\n      stdout.println(`Left padded: [${leftPad}]`)\n\n      // === FORMATTING COLUMNS ===\n\n      names <- [\"Alice\", \"Bob\", \"Charlie\"]\n      amounts <- [\"1234\", \"56\", \"789\"]\n\n      idx <- 0\n      for name in names\n        amount <- amounts.getOrDefault(idx, \"0\")\n        stdout.println(`${name.rightPadded(12)} ${amount.leftPadded(8)}`)\n        idx := idx + 1\n\n      // === STRING LONGER THAN WIDTH ===\n\n      longStr <- \"A very long string\"\n      padded <- longStr.rightPadded(5)\n      stdout.println(`Long string padded to 5: [${padded}]`)","migrationContext":"Java: String.format(\"%-20s\", str) for left-align, String.format(\"%20s\", str) for right-align. Python: str.ljust(20), str.rjust(20). Rust: format!(\"{:<20}\", str), format!(\"{:>20}\", str). Go: fmt.Sprintf(\"%-20s\", str). JavaScript: str.padEnd(20), str.padStart(20). Kotlin: str.padEnd(20), str.padStart(20). EK9: str.rightPadded(20), str.leftPadded(20).","keywords":["align","column","fixed","format","left","pad","padding","right","string","text","width"],"primaryTopics":[],"typicalErrors":[{"error":"E50060","correct":"rightPad <- word.rightPadded(20)","incorrect":"rightPad <- word.padEnd(20)","explanation":"String has no padEnd() method. The correct method names in EK9 are rightPadded() and leftPadded(). See ek9 -h E50060 for details."},{"error":"E11068","correct":"stdout.println(`${name.rightPadded(12)} ${amount.leftPadded(8)}`)","incorrect":"stdout.println(name.rightPadded(12) + \" \" + amount.leftPadded(8))","explanation":"Concatenating 3 or more string parts with + triggers E11068. Use backtick interpolation instead. See ek9 -h E11068 for details."}],"companions":[]}
{"id":177,"category":"Common String Operations","question":"How do I check if a string starts or ends with a value in EK9?","url":"https://ek9.io/qa/QA0177.html","alternatePhrasings":["Does EK9 have startsWith or endsWith?","How do I check for a string prefix in EK9?","How do I check for a string suffix in EK9?"],"answer":"EK9 does not have Swift/Go-style .hasPrefix() or .hasSuffix() methods. Use the matches operator with regex anchors for prefix and suffix checks.\n\nSTARTS WITH (PREFIX)\nUse regex with ^ anchor:\n  if myString matches /^Hello.*/\n    stdout.println(\"Starts with Hello\")\n\nENDS WITH (SUFFIX)\nUse regex with $ anchor:\n  if myString matches /.*World$/\n    stdout.println(\"Ends with World\")\n\nFIRST AND LAST CHARACTER\nFor single character checks, use the first() and last() methods:\n  firstChar <- myString.first()\n  lastChar <- myString.last()\n\nWHY NO HASPREFIX/HASSUFFIX\nEK9 keeps the String API focused. The matches operator with regex provides the same functionality with more flexibility: you can match complex patterns, not just fixed prefixes.\n\nSee Q37 for string methods. See Q33 for regular expressions. See Q178 for first/last character access.\n\nSee Q37 for strings. See Q171 for string contains.","ek9Example":"defines module qa.stringops.startsends\n\n  defines program\n    StringStartsEndsDemo()\n      stdout <- Stdout()\n\n      greeting <- \"Hello World\"\n\n      // === STARTS WITH (PREFIX) ===\n\n      if greeting matches /^Hello.*/\n        stdout.println(\"Starts with Hello\")\n\n      if greeting matches /^Goodbye.*/\n        stdout.println(\"Should not print\")\n\n      // === ENDS WITH (SUFFIX) ===\n\n      if greeting matches /.*World$/\n        stdout.println(\"Ends with World\")\n\n      if greeting matches /.*Earth$/\n        stdout.println(\"Should not print\")\n\n      // === FIRST AND LAST CHARACTER ===\n\n      firstCh <- greeting.first()\n      lastCh <- greeting.last()\n      stdout.println(`First character: ${firstCh}`)\n      stdout.println(`Last character: ${lastCh}`)\n\n      // === PRACTICAL: FILE EXTENSION CHECK ===\n\n      filename <- \"report.csv\"\n      if filename matches /.*\\.csv$/\n        stdout.println(\"CSV file detected\")\n\n      // === PRACTICAL: PROTOCOL CHECK ===\n\n      url <- \"https://example.com\"\n      if url matches /^https.*/\n        stdout.println(\"Secure URL\")","migrationContext":"Java: str.startsWith(prefix), str.endsWith(suffix). Python: str.startswith(prefix), str.endswith(suffix). Rust: str.starts_with(prefix), str.ends_with(suffix). Go: strings.HasPrefix(), strings.HasSuffix(). JavaScript: str.startsWith(), str.endsWith(). Kotlin: str.startsWith(), str.endsWith(). EK9: str matches /^prefix.*/, str matches /.*suffix$/, no hasPrefix()/hasSuffix() methods.","keywords":["begins","ends","endsWith","matches","prefix","regex","starts","startsWith","string","suffix","text"],"primaryTopics":["starts with","ends with","string prefix"],"typicalErrors":[{"error":"E50060","correct":"if greeting matches /^Hello.*/","incorrect":"if greeting.hasPrefix(\"Hello\")","explanation":"String has no hasPrefix() or hasSuffix() methods (common in Swift and Go). Use the matches operator with regex anchors (^, $) for prefix and suffix checks. See ek9 -h E50060 for details."}],"companions":[]}
{"id":178,"category":"Common String Operations","question":"How do I get the first or last character of a string in EK9?","url":"https://ek9.io/qa/QA0178.html","alternatePhrasings":["How do I extract a single character from a string in EK9?","What is the EK9 equivalent of charAt()?","How do I access characters by position in EK9?"],"answer":"EK9 provides .first() and .last() methods for accessing the first and last characters. For characters at other positions, use stream skip and head.\n\nFIRST CHARACTER\n  firstCh <- myString.first()\nReturns a Character. Returns unset Character if the string is empty.\n\nLAST CHARACTER\n  lastCh <- myString.last()\nReturns the last Character.\n\nCHARACTER AT POSITION\nUse stream skip and head to extract a character at a specific index:\n  thirdChar <- cat myString | skip 2 | head 1 | collect as String\nSkip 2, take 1 gives the character at index 2 (zero-based).\n\nSUBSTRING EXTRACTION\nUse skip and head for any substring:\n  sub <- cat myString | skip 6 | head 5 | collect as String\n\nSee Q37 for streamable strings. See Q38 for the Character type. See Q177 for string starts/ends. See Q179 for string fuzzy match.","ek9Example":"defines module qa.stringops.firstlast\n\n  defines program\n    StringFirstLastDemo()\n      stdout <- Stdout()\n\n      greeting <- \"Hello World\"\n\n      // === FIRST CHARACTER ===\n\n      firstCh <- greeting.first()\n      stdout.println(`First: ${firstCh}`)\n\n      // === LAST CHARACTER ===\n\n      lastCh <- greeting.last()\n      stdout.println(`Last: ${lastCh}`)\n\n      // === CHARACTER AT POSITION (via stream) ===\n\n      // Get character at index 6 (zero-based)\n      charAtSix <- cat greeting | skip 6 | head 1 | collect as String\n      stdout.println(`Char at 6: ${charAtSix}`)\n\n      // === SUBSTRING EXTRACTION ===\n\n      // Extract \"World\" (skip 6, take 5)\n      world <- cat greeting | skip 6 | head 5 | collect as String\n      stdout.println(`Substring: ${world}`)\n\n      // Extract first 5 characters\n      hello <- cat greeting | head 5 | collect as String\n      stdout.println(`First 5: ${hello}`)\n\n      // === EMPTY STRING SAFETY ===\n\n      emptyStr <- \"\"\n      emptyFirst <- emptyStr.first()\n      emptyLast <- emptyStr.last()\n      stdout.println(`Empty first isSet: ${emptyFirst?}`)\n      stdout.println(`Empty last isSet: ${emptyLast?}`)","migrationContext":"Java: str.charAt(0), str.charAt(str.length()-1). Python: str[0], str[-1]. Rust: str.chars().next(), str.chars().last(). Go: str[0] (byte, not char). JavaScript: str[0], str.at(-1). Kotlin: str.first(), str.last(). EK9: str.first(), str.last(), stream skip+head for any position.","keywords":["char","character","extract","first","head","index","last","position","string","tail","text"],"primaryTopics":[],"typicalErrors":[{"error":"E50060","correct":"firstCh <- greeting.first()","incorrect":"firstCh <- greeting.charAt(0)","explanation":"String has no charAt() method. Use .first() for the first character, .last() for the last, or stream skip+head for arbitrary positions. See ek9 -h E50060 for details."}],"companions":[]}
{"id":179,"category":"Common String Operations","question":"How do I compare strings for fuzzy or approximate matching in EK9?","url":"https://ek9.io/qa/QA0179.html","alternatePhrasings":["Does EK9 have Levenshtein distance?","How do I find similar strings in EK9?","How do I detect typos or approximate matches in EK9?"],"answer":"EK9 has a built-in Levenshtein distance operator for fuzzy string comparison. The <~> operator returns the edit distance between two strings as an Integer.\n\nLEVENSHTEIN DISTANCE (<~>)\nCompute edit distance between two strings:\n  dist <- str1 <~> str2\nReturns the minimum number of single-character edits (insertions, deletions, substitutions) needed to transform one string into the other.\n\nIDENTICAL STRINGS\n  \"hello\" <~> \"hello\" returns 0\n\nSINGLE EDIT\n  \"kitten\" <~> \"sitten\" returns 1 (one substitution)\n\nPRACTICAL USE: TYPO DETECTION\nUse a threshold to detect likely typos:\n  if (input <~> expected) <= 2\n    stdout.println(\"Close enough\")\n\nREGEX FOR PATTERN MATCHING\nFor pattern-based matching rather than distance:\n  if myString matches /pattern/\n\nSee Q37 for string basics. See Q33 for regular expressions.\n\nSee Q37 for strings. See Q33 for regular expressions. See Q239 for the <~> fuzzy comparison operator used in ordering and matching.","ek9Example":"defines module qa.stringops.fuzzymatch\n\n  defines program\n    FuzzyMatchDemo()\n      stdout <- Stdout()\n\n      // === LEVENSHTEIN DISTANCE (<~>) ===\n\n      // Identical strings — distance 0\n      greeting <- \"hello\"\n      sameGreeting <- \"hello\"\n      dist0 <- greeting <~> sameGreeting\n      stdout.println(`hello <~> hello = ${dist0}`)\n\n      // One substitution — distance 1\n      original <- \"kitten\"\n      dist1 <- original <~> \"sitten\"\n      stdout.println(`kitten <~> sitten = ${dist1}`)\n\n      // Multiple edits\n      dist3 <- original <~> \"sitting\"\n      stdout.println(`kitten <~> sitting = ${dist3}`)\n\n      // Empty string\n      planet <- \"world\"\n      distEmpty <- planet <~> \"\"\n      stdout.println(`world <~> empty = ${distEmpty}`)\n\n      // === PRACTICAL: TYPO DETECTION ===\n\n      expected <- \"function\"\n      typo <- \"funciton\"\n      typoDistance <- expected <~> typo\n      stdout.println(`Typo distance: ${typoDistance}`)\n\n      typoThreshold <- 2\n      if typoDistance <= typoThreshold\n        stdout.println(\"Close enough - likely a typo\")\n\n      // === PRACTICAL: FIND CLOSEST MATCH ===\n\n      input <- \"colr\"\n      target1 <- \"color\"\n      target2 <- \"collar\"\n      target3 <- \"column\"\n\n      d1 <- input <~> target1\n      d2 <- input <~> target2\n      d3 <- input <~> target3\n      stdout.println(`colr <~> color = ${d1}`)\n      stdout.println(`colr <~> collar = ${d2}`)\n      stdout.println(`colr <~> column = ${d3}`)\n\n      // === REGEX FOR PATTERN MATCHING ===\n\n      testPhrase <- \"Hello World\"\n      if testPhrase matches /.*World$/\n        stdout.println(\"Pattern match: ends with World\")","migrationContext":"Java: no built-in Levenshtein, requires Apache Commons or custom implementation. Python: no built-in, requires python-Levenshtein or difflib.SequenceMatcher. Rust: no built-in, requires strsim crate. Go: no built-in, requires third-party packages. JavaScript: no built-in, requires libraries. Kotlin: no built-in. EK9: built-in <~> operator returns Levenshtein distance as Integer, no libraries needed.","keywords":["approximate","compare","distance","fuzzy","levenshtein","match","regex","similar","string","text","typo"],"primaryTopics":[],"typicalErrors":[{"error":"E50060","correct":"dist1 <- original <~> \"sitten\"","incorrect":"dist1 <- original.levenshtein(\"sitten\")","explanation":"String has no levenshtein() method. Use the <~> operator for Levenshtein distance. See ek9 -h E50060 for details."}],"companions":[]}
{"id":180,"category":"Common Collection Tasks","question":"How do I add an item to a list in EK9?","url":"https://ek9.io/qa/QA0180.html","alternatePhrasings":["How do I append an element to a list in EK9?","What is the EK9 equivalent of list.add()?","How do I push an item onto a list in EK9?"],"answer":"EK9 provides two addition operators: += for mutating the list and + for creating a new list.\n\nMUTATING ADD (+=)\nAdd an item to the existing list:\n  numbers += 4\nModifies the list in place.\n\nNEW LIST (+)\nCreate a new list with the item added:\n  extended <- numbers + 4\nThe original list is unchanged.\n\nADD MULTIPLE ITEMS\nMerge another list using :~: or +:\n  numbers :~: moreNumbers\n  combined <- numbers + moreNumbers\n\nSee Q45 for List basics. See Q88 for List operations. See Q186 for merging collections. See Q181 for list remove.","ek9Example":"defines module qa.collectiontasks.listadd\n\n  defines program\n    ListAddDemo()\n      stdout <- Stdout()\n\n      // === MUTATING ADD (+=) ===\n\n      numbers <- [1, 2, 3]\n      stdout.println(`Before: ${numbers}`)\n\n      numbers += 4\n      stdout.println(`After += 4: ${numbers}`)\n\n      numbers += 5\n      stdout.println(`After += 5: ${numbers}`)\n\n      // === NEW LIST (+) ===\n\n      original <- [10, 20]\n      extended <- original + 30\n      stdout.println(`Original: ${original}`)\n      stdout.println(`Extended: ${extended}`)\n\n      // === ADD MULTIPLE ITEMS (:~:) ===\n\n      base <- [1, 2, 3]\n      extras <- [4, 5, 6]\n      base :~: extras\n      stdout.println(`After merge: ${base}`)\n\n      // === ADD MULTIPLE WITH + ===\n\n      left <- [1, 2]\n      right <- [3, 4]\n      combined <- left + right\n      stdout.println(`Combined: ${combined}`)\n      stdout.println(`Left unchanged: ${left}`)\n\n      // === BUILD FROM EMPTY ===\n\n      built <- List() of String\n      built += \"first\"\n      built += \"second\"\n      built += \"third\"\n      stdout.println(`Built: ${built}`)","migrationContext":"Java: list.add(item), Collections.unmodifiableList for immutable. Python: list.append(item), list.extend(). Rust: vec.push(item). Go: append(slice, item). JavaScript: arr.push(item), [...arr, item] for new array. Kotlin: mutableList.add(item), list + item. EK9: list += item for mutating, list + item for new list.","keywords":["add","append","collection","element","insert","item","list","mutate","plus","push","task"],"primaryTopics":["add to list","list add","append"],"typicalErrors":[{"error":"E50060","correct":"numbers += 4","incorrect":"numbers.add(4)","explanation":"EK9 uses the += operator to append items to a list, not an add() method. The operator syntax is consistent across all collection types. See ek9 -h E50060 for details."},{"error":"E50060","correct":"base :~: extras","incorrect":"base.addAll(extras)","explanation":"EK9 uses the :~: merge operator to add all items from one list to another, not an addAll() method. See ek9 -h E50060 for details."}],"companions":[]}
{"id":181,"category":"Common Collection Tasks","question":"How do I remove an item from a list in EK9?","url":"https://ek9.io/qa/QA0181.html","alternatePhrasings":["How do I delete an element from a list in EK9?","What is the EK9 equivalent of list.remove()?","How do I drop an item from a list in EK9?"],"answer":"EK9 provides two removal operators: -= for mutating the list and - for creating a new list without the item.\n\nMUTATING REMOVE (-=)\nRemove the first occurrence of an item:\n  numbers -= 3\nModifies the list in place. If the item is not found, no change.\n\nNEW LIST (-)\nCreate a new list without the item:\n  filtered <- numbers - 3\nThe original list is unchanged.\n\nSTREAM FILTER FOR COMPLEX REMOVAL\nRemove items matching a condition using stream pipeline:\n  cat numbers | filter by isPositive | collect as List of Integer\n\nSee Q45 for List basics. See Q88 for List operations. See Q89 for stream pipelines.","ek9Example":"defines module qa.collectiontasks.listremove\n\n  defines function\n\n    isGreaterThanTwo() as pure\n      -> num as Integer\n      <- rtn <- Boolean()\n      expectedSize <- 2\n      rtn: num > expectedSize\n\n  defines program\n    ListRemoveDemo()\n      stdout <- Stdout()\n\n      // === MUTATING REMOVE (-=) ===\n\n      numbers <- [1, 2, 3, 4, 5]\n      stdout.println(`Before: ${numbers}`)\n\n      numbers -= 3\n      stdout.println(`After -= 3: ${numbers}`)\n\n      // Remove item not in list — no change\n      numbers -= 99\n      stdout.println(`After -= 99 (no change): ${numbers}`)\n\n      // === NEW LIST (-) ===\n\n      original <- [10, 20, 30, 40]\n      without30 <- original - 30\n      stdout.println(`Original: ${original}`)\n      stdout.println(`Without 30: ${without30}`)\n\n      // === STREAM FILTER FOR COMPLEX REMOVAL ===\n\n      // Keep only items greater than 2\n      mixed <- [1, 2, 3, 4, 5]\n      filtered <- cat mixed | filter by isGreaterThanTwo | collect as List of Integer\n      stdout.println(`Filtered (> 2): ${filtered}`)\n\n      // === REMOVE FROM STRINGS ===\n\n      names <- [\"Alice\", \"Bob\", \"Charlie\"]\n      names -= \"Bob\"\n      stdout.println(`After removing Bob: ${names}`)","migrationContext":"Java: list.remove(item), list.removeIf(predicate). Python: list.remove(item), list comprehension for filtered. Rust: vec.retain(predicate), vec.remove(index). Go: manual loop and slice manipulation. JavaScript: arr.filter(), splice for in-place. Kotlin: list.minus(item), mutableList.remove(). EK9: list -= item for mutating, list - item for new list, stream filter for complex removal.","keywords":["collection","delete","drop","element","filter","item","list","minus","mutate","remove","task"],"primaryTopics":["remove from list","list remove"],"typicalErrors":[{"error":"E50060","correct":"numbers -= 3","incorrect":"numbers.remove(3)","explanation":"EK9 uses the -= operator to remove items from a list, not a remove() method. See ek9 -h E50060 for details."}],"companions":[]}
{"id":182,"category":"Common Collection Tasks","question":"How do I check if a list or dict is empty in EK9?","url":"https://ek9.io/qa/QA0182.html","alternatePhrasings":["How do I test if a collection has no elements in EK9?","What is the EK9 equivalent of isEmpty()?","How do I check the size of a collection in EK9?"],"answer":"EK9 provides the is empty operator and the length operator for checking collection emptiness and size.\n\nIS EMPTY\nTest whether a collection has no elements:\n  if myList is empty\n    stdout.println(\"No items\")\nWorks on List, Dict, and String.\n\nLENGTH\nGet the number of elements:\n  count <- length myList\nReturns Integer.\n\nIS NOT EMPTY\nNegate the empty check:\n  if ~myList is empty\n    stdout.println(\"Has items\")\n\nCRITICAL: EMPTY IS SET\nAn empty collection IS set. List() of T creates a valid, set, empty list. Empty does not mean unset. The ? operator returns true for empty collections because they are meaningful values.\n\nSee Q29 for tri-state semantics. See Q45 for List basics. See Q46 for Dict basics.","ek9Example":"defines module qa.collectiontasks.emptycheck\n\n  defines function\n    getList()\n      <- rtn as List of Integer: List() of Integer\n\n  defines program\n    CollectionEmptyDemo()\n      stdout <- Stdout()\n\n      // === LIST IS EMPTY ===\n\n      emptyList <- getList()\n      stdout.println(`Empty list is empty: ${emptyList is empty}`)\n      stdout.println(`Empty list length: ${length emptyList}`)\n\n      filledList <- [1, 2, 3]\n      stdout.println(`Filled list is empty: ${filledList is empty}`)\n      stdout.println(`Filled list length: ${length filledList}`)\n\n      // === DICT IS EMPTY ===\n\n      emptyDict <- Dict() of (String, Integer)\n      stdout.println(`Empty dict is empty: ${emptyDict is empty}`)\n      stdout.println(`Empty dict length: ${length emptyDict}`)\n\n      filledDict <- {\"a\": 1, \"b\": 2}\n      stdout.println(`Filled dict is empty: ${filledDict is empty}`)\n      stdout.println(`Filled dict length: ${length filledDict}`)\n\n      // === IS NOT EMPTY ===\n\n      if ~filledList is empty\n        stdout.println(\"Filled list has items\")\n\n      if emptyList is empty\n        stdout.println(\"Empty list has no items\")\n\n      // === CRITICAL: EMPTY IS SET ===\n\n      // Empty list IS set — it is a valid, meaningful value\n      require emptyList?\n      stdout.println(`Empty list isSet: ${emptyList?}`)\n\n      // Empty dict IS set\n      require emptyDict?\n      stdout.println(`Empty dict isSet: ${emptyDict?}`)\n\n      // === STRING EMPTY CHECK ===\n\n      emptyStr <- \"\"\n      fullStr <- \"Hello\"\n      stdout.println(`Empty string is empty: ${emptyStr is empty}`)\n      stdout.println(`Full string is empty: ${fullStr is empty}`)","migrationContext":"Java: list.isEmpty(), list.size(). Python: len(list) == 0, not list for truthiness. Rust: vec.is_empty(), vec.len(). Go: len(slice) == 0. JavaScript: arr.length === 0. Kotlin: list.isEmpty(), list.size. EK9: list is empty, length list, empty collections are set (not unset).","keywords":["check","collection","count","dict","empty","length","list","set","size","task","unset","zero"],"primaryTopics":["empty check","is empty"],"typicalErrors":[{"error":"E50060","correct":"emptyList is empty","incorrect":"emptyList.isEmpty()","explanation":"EK9 uses the 'is empty' operator to check if a collection has no elements, not an isEmpty() method. See ek9 -h E50060 for details."},{"error":"E50060","correct":"length filledList","incorrect":"filledList.size()","explanation":"EK9 uses the prefix 'length' operator to get collection size, not a size() method. See ek9 -h E50060 for details."}],"companions":[]}
{"id":183,"category":"Common Collection Tasks","question":"How do I get the first or last item from a list in EK9?","url":"https://ek9.io/qa/QA0183.html","alternatePhrasings":["How do I peek at the first element of a list in EK9?","How do I access the front or back of a list in EK9?","What is the EK9 equivalent of list.first() or list.last()?"],"answer":"EK9 provides getOrDefault(0, default) for the first element and getOrDefault(length-1, default) for the last. Stream head and tail provide alternative approaches.\n\nFIRST ELEMENT\nSafe access with getOrDefault:\n  first <- myList.getOrDefault(0, defaultVal)\nReturns the first element or the default if the list is empty.\n\nLAST ELEMENT\nAccess the last element by index:\n  last <- myList.getOrDefault(length myList - 1, defaultVal)\n\nSTREAM HEAD FOR FIRST\nUse head 1 in a stream pipeline:\n  firstItems <- cat myList | head 1 | collect as List of Integer\nReturns a list with 0 or 1 elements.\n\nSTREAM TAIL FOR LAST\nUse tail 1 in a stream pipeline:\n  lastItems <- cat myList | tail 1 | collect as List of Integer\n\nSee Q45 for List basics. See Q162 for safe list access. See Q125 for head/tail/skip in streams.","ek9Example":"defines module qa.collectiontasks.listfirstlast\n\n  defines program\n    ListFirstLastDemo()\n      stdout <- Stdout()\n\n      numbers <- [10, 20, 30, 40, 50]\n\n      // === FIRST ELEMENT ===\n\n      first <- numbers.getOrDefault(0, -1)\n      stdout.println(`First: ${first}`)\n\n      // === LAST ELEMENT ===\n\n      lastIndex <- length numbers - 1\n      last <- numbers.getOrDefault(lastIndex, -1)\n      stdout.println(`Last: ${last}`)\n\n      // === STREAM HEAD FOR FIRST ===\n\n      firstViaStream <- cat numbers | head 1 | collect as List of Integer\n      stdout.println(`Head 1: ${firstViaStream}`)\n\n      // === STREAM TAIL FOR LAST ===\n\n      lastViaStream <- cat numbers | tail 1 | collect as List of Integer\n      stdout.println(`Tail 1: ${lastViaStream}`)\n\n      // === EMPTY LIST SAFETY ===\n\n      emptyList <- List() of Integer\n      emptyFirst <- emptyList.getOrDefault(0, -1)\n      stdout.println(`Empty first (default -1): ${emptyFirst}`)\n\n      emptyHead <- cat emptyList | head 1 | collect as List of Integer\n      stdout.println(`Empty head: ${emptyHead}`)\n\n      // === SECOND ELEMENT ===\n\n      second <- numbers.getOrDefault(1, -1)\n      stdout.println(`Second: ${second}`)","migrationContext":"Java: list.get(0) throws if empty, list.getFirst()/getLast() in Java 21+. Python: list[0], list[-1] throw IndexError if empty. Rust: vec.first(), vec.last() return Option. Go: slice[0] panics if empty. JavaScript: arr[0], arr.at(-1). Kotlin: list.first(), list.last() throw, list.firstOrNull(). EK9: getOrDefault(0, default) always safe, stream head/tail for pipeline approach.","keywords":["back","collection","element","first","front","getOrDefault","head","last","list","peek","tail","task"],"primaryTopics":[],"typicalErrors":[{"error":"E50060","correct":"first <- numbers.getOrDefault(0, -1)","incorrect":"first <- numbers.get(0)","explanation":"EK9 List has no get() method. Use getOrDefault(index, default) for safe indexed access that always returns a value. See ek9 -h E50060 for details."},{"error":"E50060","correct":"firstViaStream <- cat numbers | head 1 | collect as List of Integer","incorrect":"firstViaStream <- numbers.first()","explanation":"EK9 List has no first() method. Use getOrDefault(0, default) or stream head 1 to safely access the first element. See ek9 -h E50060 for details."}],"companions":[]}
{"id":184,"category":"Common Collection Tasks","question":"How do I find the first item matching a condition in EK9?","url":"https://ek9.io/qa/QA0184.html","alternatePhrasings":["What is the EK9 equivalent of find() or findFirst()?","How do I search a list for the first match in EK9?","How do I filter a list and take the first result in EK9?"],"answer":"EK9 uses stream pipelines with filter and head 1 to find the first matching item.\n\nFILTER + HEAD 1\nThe standard pattern for finding the first match:\n  results <- cat items | filter by predicate | head 1 | collect as List of Integer\nReturns a list with 0 or 1 elements. Check if empty to determine whether a match was found.\n\nCHECK RESULT\n  if ~results is empty\n    found <- results.getOrDefault(0, defaultVal)\n    stdout.println(`Found: ${found}`)\n\nWHY HEAD 1\nhead 1 stops processing after the first match is found. This is EK9's equivalent of early exit: the stream terminates as soon as one element passes through.\n\nSee Q89 for stream pipelines. See Q125 for head/tail/skip. See Q162 for safe list access. See Q185 for any/all match.","ek9Example":"defines module qa.collectiontasks.findfirst\n\n  defines function\n\n    isEven() as pure\n      -> num as Integer\n      <- rtn as Boolean: num mod 2 == 0\n\n    isLong() as pure\n      -> str as String\n      <- rtn <- Boolean()\n      expectedValue <- 4\n      rtn: length str > expectedValue\n\n  defines program\n    FindFirstMatchDemo()\n      stdout <- Stdout()\n\n      // === FILTER + HEAD 1 ===\n\n      numbers <- [1, 3, 5, 4, 6, 8]\n      firstEven <- cat numbers | filter by isEven | head 1 | collect as List of Integer\n      stdout.println(`First even: ${firstEven}`)\n\n      // === CHECK IF FOUND ===\n\n      if ~firstEven is empty\n        found <- firstEven.getOrDefault(0, 0)\n        stdout.println(`Found: ${found}`)\n\n      // === NO MATCH FOUND ===\n\n      allOdd <- [1, 3, 5, 7, 9]\n      noMatch <- cat allOdd | filter by isEven | head 1 | collect as List of Integer\n      stdout.println(`No match result: ${noMatch}`)\n\n      if noMatch is empty\n        stdout.println(\"No even number found\")\n\n      // === FIND FIRST IN STRINGS ===\n\n      names <- [\"Bob\", \"Jo\", \"Alice\", \"Charlie\"]\n      longNames <- cat names | filter by isLong | head 1 | collect as List of String\n      stdout.println(`First long name: ${longNames}`)\n\n      // === ALL MATCHES (WITHOUT HEAD) ===\n\n      allEvens <- cat numbers | filter by isEven | collect as List of Integer\n      stdout.println(`All evens: ${allEvens}`)","migrationContext":"Java: list.stream().filter(pred).findFirst() returns Optional. Python: next((x for x in items if pred(x)), default). Rust: iter.find(pred) returns Option. Go: manual loop with break. JavaScript: arr.find(pred). Kotlin: list.find(pred), list.firstOrNull(pred). EK9: cat list | filter by pred | head 1 | collect as List, no break needed.","keywords":["collection","condition","filter","find","first","head","match","pipeline","predicate","search","stream","task"],"primaryTopics":[],"typicalErrors":[{"error":"E07520","correct":"<- rtn as Boolean: num mod 2 == 0","incorrect":"<- rtn as String: $num","explanation":"A predicate function used with 'filter by' must return Boolean. Returning String instead of Boolean triggers E07520 because the stream filter needs a true/false decision. See ek9 -h E07520 for details."}],"companions":[]}
{"id":185,"category":"Common Collection Tasks","question":"How do I check if any or all items match a condition in EK9?","url":"https://ek9.io/qa/QA0185.html","alternatePhrasings":["What is the EK9 equivalent of anyMatch() or allMatch()?","How do I check if every element satisfies a condition in EK9?","How do I check if at least one element matches in EK9?"],"answer":"EK9 uses stream pipelines with filter and empty checks to implement any-match and all-match patterns.\n\nANY MATCH\nCheck if at least one item matches by filtering and checking for non-empty results:\n  matches <- cat items | filter by predicate | head 1 | collect as List of Integer\n  if ~matches is empty\n    stdout.println(\"At least one matches\")\nhead 1 stops as soon as one match is found.\n\nALL MATCH\nCheck if ALL items match by filtering for the negation and checking for empty results:\n  failures <- cat items | filter by negate | head 1 | collect as List of Integer\n  if failures is empty\n    stdout.println(\"All items match\")\nIf no items fail the condition, then all items pass.\n\nNONE MATCH\nCheck if NO items match:\n  anyFound <- cat items | filter by predicate | head 1 | collect as List of Integer\n  if anyFound is empty\n    stdout.println(\"None match\")\n\nSee Q89 for stream pipelines. See Q184 for finding the first match. See Q125 for head/tail/skip.","ek9Example":"defines module qa.collectiontasks.anyallmatch\n\n  defines function\n\n    isPositive() as pure\n      -> num as Integer\n      <- rtn as Boolean: num > 0\n\n    isNotPositive() as pure\n      -> num as Integer\n      <- rtn as Boolean: num <= 0\n\n    isEven() as pure\n      -> num as Integer\n      <- rtn as Boolean: num mod 2 == 0\n\n    isNotEven() as pure\n      -> num as Integer\n      <- rtn as Boolean: num mod 2 <> 0\n\n  defines program\n    AnyAllMatchDemo()\n      stdout <- Stdout()\n\n      numbers <- [1, 2, 3, 4, 5]\n\n      // === ANY MATCH ===\n\n      // Check if any number is even\n      anyEven <- cat numbers | filter by isEven | head 1 | collect as List of Integer\n      if ~anyEven is empty\n        stdout.println(\"At least one even number found\")\n\n      // === ALL MATCH ===\n\n      // Check if ALL numbers are positive\n      notPositive <- cat numbers | filter by isNotPositive | head 1 | collect as List of Integer\n      if notPositive is empty\n        stdout.println(\"All numbers are positive\")\n\n      // === NONE MATCH ===\n\n      // Check if NONE are negative (all positive)\n      anyNegative <- cat numbers | filter by isNotPositive | head 1 | collect as List of Integer\n      if anyNegative is empty\n        stdout.println(\"No negative numbers found\")\n\n      // === ALL EVEN CHECK (will fail) ===\n\n      notEven <- cat numbers | filter by isNotEven | head 1 | collect as List of Integer\n      if notEven is empty\n        stdout.println(\"All even\")\n      else\n        stdout.println(\"Not all numbers are even\")\n\n      // === ANY MATCH ON EMPTY LIST ===\n\n      emptyList <- List() of Integer\n      anyInEmpty <- cat emptyList | filter by isEven | head 1 | collect as List of Integer\n      if anyInEmpty is empty\n        stdout.println(\"Empty list: no matches (any returns false)\")","migrationContext":"Java: stream.anyMatch(pred), stream.allMatch(pred), stream.noneMatch(pred). Python: any(pred(x) for x in items), all(pred(x) for x in items). Rust: iter.any(pred), iter.all(pred). Go: manual loops. JavaScript: arr.some(pred), arr.every(pred). Kotlin: list.any(pred), list.all(pred), list.none(pred). EK9: filter + head 1 + empty check for any, filter by negation + empty check for all.","keywords":["all","any","check","collection","condition","every","filter","match","none","predicate","some","stream","task"],"primaryTopics":[],"typicalErrors":[{"error":"E50060","correct":"stdout.println(\"At least one even number found\")","incorrect":"stdout.display(\"At least one even number found\")","explanation":"Stdout does not have a display() method. The correct method is println(). Calling a non-existent method triggers E50060 — method not resolved. See ek9 -h E50060 for details."}],"companions":[]}
{"id":186,"category":"Common Collection Tasks","question":"How do I merge two lists or two dicts in EK9?","url":"https://ek9.io/qa/QA0186.html","alternatePhrasings":["How do I combine two collections in EK9?","What is the EK9 equivalent of addAll() or putAll()?","How do I join or concatenate two lists in EK9?"],"answer":"EK9 provides the :~: merge operator for in-place merging and the + operator for creating new merged collections.\n\nLIST MERGE (:~:)\nAppend all items from source to target:\n  target :~: source\nModifies target in place.\n\nLIST PLUS (+)\nCreate a new combined list:\n  combined <- list1 + list2\nBoth originals unchanged.\n\nDICT MERGE (:~:)\nMerge entries from source into target:\n  target :~: source\nWhen keys overlap, the source values win.\n\nDICT PLUS (+)\nCreate a new merged dict:\n  combined <- dict1 + dict2\n\nCOPY (:=:)\nDeep copy one collection into another:\n  target :=: source\nReplaces the target contents entirely.\n\nSee Q88 for List operations. See Q90 for Dict operations. See Q130 for mutating vs non-mutating operators.","ek9Example":"defines module qa.collectiontasks.mergecollections\n\n  defines program\n    MergeCollectionsDemo()\n      stdout <- Stdout()\n\n      // === LIST MERGE (:~:) ===\n\n      list1 <- [1, 2, 3]\n      list2 <- [4, 5, 6]\n      list1 :~: list2\n      stdout.println(`List merge: ${list1}`)\n\n      // === LIST PLUS (+) ===\n\n      left <- [10, 20]\n      right <- [30, 40]\n      combined <- left + right\n      stdout.println(`List +: ${combined}`)\n      stdout.println(`Left unchanged: ${left}`)\n\n      // === DICT MERGE (:~:) ===\n\n      d1 <- {\"a\": 1, \"b\": 2}\n      d2 <- {\"b\": 99, \"c\": 3}\n      d1 :~: d2\n      stdout.println(`Dict merge: ${d1}`)\n\n      // === DICT PLUS (+) ===\n\n      base <- {\"x\": 10}\n      extra <- {\"y\": 20, \"z\": 30}\n      merged <- base + extra\n      stdout.println(`Dict +: ${merged}`)\n      stdout.println(`Base unchanged: ${base}`)\n\n      // === COPY (:=:) ===\n\n      source <- [100, 200, 300]\n      target <- List() of Integer\n      target :=: source\n      stdout.println(`Copied list: ${target}`)\n\n      srcDict <- {\"key\": 42}\n      tgtDict <- Dict() of (String, Integer)\n      tgtDict :=: srcDict\n      stdout.println(`Copied dict: ${tgtDict}`)\n\n      // === MERGE SINGLE ITEM ===\n\n      names <- [\"Alice\", \"Bob\"]\n      names :~: \"Charlie\"\n      stdout.println(`After merge single: ${names}`)","migrationContext":"Java: list.addAll(other), map.putAll(other), no operator syntax. Python: list.extend(other), dict.update(other), | for dict union. Rust: vec.extend(other), map.extend(other). Go: append(slice1, slice2...), manual loop for maps. JavaScript: [...arr1, ...arr2], Object.assign(). Kotlin: list + other, map + other. EK9: :~: merge operator, + for new combined collection, :=: for deep copy.","keywords":["append","collection","combine","concatenate","copy","dict","join","list","merge","task","union"],"primaryTopics":[],"typicalErrors":[{"error":"E50060","correct":"list1 :~: list2","incorrect":"list1.addAll(list2)","explanation":"EK9 uses the :~: merge operator for in-place collection merging, not an addAll() method. See ek9 -h E50060 for details."},{"error":"E50060","correct":"d1 :~: d2","incorrect":"d1.putAll(d2)","explanation":"EK9 uses the :~: merge operator for merging Dict entries, not a putAll() method. When keys overlap, source values win. See ek9 -h E50060 for details."}],"companions":[]}
{"id":187,"category":"Common Collection Tasks","question":"How do I reverse a list in EK9?","url":"https://ek9.io/qa/QA0187.html","alternatePhrasings":["How do I flip the order of a list in EK9?","What is the EK9 equivalent of list.reverse()?","How do I invert the order of elements in a list in EK9?"],"answer":"EK9 Lists have a .reverse() method that returns a new reversed list. The original list is unchanged.\n\nREVERSE\nCreate a new list in reverse order:\n  reversed <- myList.reverse()\nThe original list is not modified.\n\nIMMUTABLE BY DEFAULT\nLike all EK9 non-mutating operations, reverse() returns a new value. To update the variable, assign the result:\n  myList :=: myList.reverse()\n\nSee Q88 for List operations. See Q120 for sorting.","ek9Example":"defines module qa.collectiontasks.reverselist\n\n  defines program\n    ReverseListDemo()\n      stdout <- Stdout()\n\n      // === REVERSE ===\n\n      numbers <- [1, 2, 3, 4, 5]\n      reversed <- numbers.reverse()\n      stdout.println(`Original: ${numbers}`)\n      stdout.println(`Reversed: ${reversed}`)\n\n      // Original is unchanged\n      require numbers <> reversed\n\n      // === REVERSE STRINGS ===\n\n      names <- [\"Alice\", \"Bob\", \"Charlie\"]\n      reversedNames <- names.reverse()\n      stdout.println(`Names: ${names}`)\n      stdout.println(`Reversed names: ${reversedNames}`)\n\n      // === DOUBLE REVERSE ===\n\n      doubleReversed <- reversed.reverse()\n      stdout.println(`Double reversed: ${doubleReversed}`)\n      require doubleReversed == numbers\n\n      // === EMPTY LIST REVERSE ===\n\n      emptyList <- List() of Integer\n      emptyReversed <- emptyList.reverse()\n      stdout.println(`Empty reversed: ${emptyReversed}`)\n      require emptyReversed is empty\n\n      // === SINGLE ITEM ===\n\n      single <- [42]\n      singleReversed <- single.reverse()\n      stdout.println(`Single reversed: ${singleReversed}`)\n      require single == singleReversed","migrationContext":"Java: Collections.reverse(list) modifies in place, no built-in immutable reverse. Python: list.reverse() modifies in place, reversed(list) returns iterator, list[::-1] for new reversed list. Rust: vec.reverse() modifies in place, .iter().rev() for lazy reverse. Go: manual loop, no built-in reverse. JavaScript: arr.reverse() modifies in place, [...arr].reverse() for new. Kotlin: list.reversed() returns new list. EK9: list.reverse() returns new list, original unchanged.","keywords":["backwards","collection","flip","invert","list","order","reverse","sort","task"],"primaryTopics":[],"typicalErrors":[{"error":"E50060","correct":"reversed <- numbers.reverse()","incorrect":"reversed <- numbers.flip()","explanation":"List does not have a flip() method. The correct method for reversing is reverse(). Calling a non-existent method triggers E50060 — method not resolved. See ek9 -h E50060 for details."}],"companions":[]}
{"id":188,"category":"JSON and Data Processing","question":"How does EK9 handle JSON as a built-in type?","url":"https://ek9.io/qa/QA0188.html","alternatePhrasings":["Is JSON a first-class type in EK9?","How do I create JSON values in EK9?","What JSON operations does EK9 support natively?"],"answer":"JSON is a first-class built-in type in EK9. You create JSON values directly with constructors and the $$ operator, without importing external libraries.\n\nJSON CONSTRUCTORS\nCreate JSON values from literals:\n  jsonNum <- JSON(42)\n  jsonStr <- JSON(\"hello\")\nCreate JSON objects with key-value pairs:\n  jsonObj <- JSON(\"name\", JSON(\"Alice\"))\n\nNATURE CHECKS\nJSON values have a nature you can query:\n  jsonObj.objectNature()   checks if JSON object\n  jsonArr.arrayNature()    checks if JSON array\n  jsonVal.valueNature()    checks if JSON primitive\n\nTO-JSON OPERATOR ($$)\nThe $$ operator converts any value to its JSON representation:\n  colour <- #FFAACC\n  jsonColour <- $$ colour\nThis works with all built-in types.\n\nSTRING CONVERSION ($)\nConvert JSON to its string form:\n  text <- $jsonObj\n\nSee Q189 for converting objects to JSON. See Q190 for parsing JSON strings. See Q191 for JSON with stream pipelines. See Q37 for String basics. See Q192 for JSON manipulate. See Q193 for JSON output.","ek9Example":"defines module qa.jsondata.firstclass\n\n  defines program\n\n    JsonFirstClassDemo()\n      stdout <- Stdout()\n\n      // === JSON CONSTRUCTORS ===\n\n      jsonNum <- JSON(42)\n      stdout.println(`JSON number: ${jsonNum}`)\n\n      jsonStr <- JSON(\"hello\")\n      stdout.println(`JSON string: ${jsonStr}`)\n\n      // JSON object with key-value pair\n      jsonObj <- JSON(\"name\", JSON(\"Alice\"))\n      stdout.println(`JSON object: ${jsonObj}`)\n\n      // === NATURE CHECKS ===\n\n      stdout.println(`Is object nature: ${jsonObj.objectNature()}`)\n      stdout.println(`Is value nature: ${jsonNum.valueNature()}`)\n\n      // === TO-JSON OPERATOR $$ ===\n\n      colour <- #FFAACC\n      jsonColour <- $$ colour\n      stdout.println(`Colour as JSON: ${jsonColour}`)\n\n      anInt <- 99\n      jsonInt <- $$ anInt\n      stdout.println(`Integer as JSON: ${jsonInt}`)\n\n      // === STRING CONVERSION $ ===\n\n      text <- $jsonObj\n      stdout.println(`JSON as string: ${text}`)","migrationContext":"Java: JSON requires external libraries (Jackson, Gson). Python: json module in stdlib but returns dict/list. Rust: serde_json crate. Go: encoding/json package. Kotlin: kotlinx.serialization. EK9: JSON is a built-in type with constructors, operators, and stream integration. No imports needed.","keywords":["array","built-in","constructor","create","data","first-class","json","nature","object","parse","serialize","type"],"primaryTopics":["JSON","JSON type","JSON support"],"typicalErrors":[{"error":"E50060","correct":"jsonObj <- JSON(\"name\", JSON(\"Alice\"))","incorrect":"jsonObj <- JSON(\"name\", \"Alice\")","explanation":"JSON constructor for key-value pairs requires both arguments to be JSON values. Passing a raw String as the value instead of JSON(String) triggers E50060 — constructor not resolved. Wrap the value with JSON(). See ek9 -h E50060 for details."}],"companions":[]}
{"id":189,"category":"JSON and Data Processing","question":"How do I convert an EK9 object to JSON?","url":"https://ek9.io/qa/QA0189.html","alternatePhrasings":["How does the $$ operator work for JSON serialization?","How do I serialize a record to JSON in EK9?","How do I convert EK9 values to JSON format?"],"answer":"EK9 uses the $$ (to-JSON) operator to convert values to JSON. Records with 'default operator $$' automatically serialize all fields to a JSON object.\n\nTO-JSON ON BUILT-IN TYPES\nAll built-in types support $$:\n  jsonInt <- $$ 42\n  jsonStr <- $$ \"hello\"\n  jsonBool <- $$ true\n\nTO-JSON ON RECORDS\nRecords with 'default operator $$' serialize all fields:\n  defines record\n    Person\n      name as String: String()\n      age as Integer: Integer()\n      default operator $$\nThen: jsonPerson <- $$ person\nThe result is a JSON object with properties for each field.\n\nUNSET FIELDS\nUnset fields in a record become JSON null values. EK9's tri-state model maps naturally to JSON:\n  set value maps to JSON value\n  unset value maps to JSON null\n  absent value maps to JSON null\n\nSee Q188 for JSON as a first-class type. See Q97 for records. See Q29 for unset variables.","ek9Example":"defines module qa.jsondata.tojson\n\n  defines record\n\n    Person\n      name as String: String()\n      age as Integer: Integer()\n\n      Person()\n        ->\n          n as String\n          a as Integer\n        name: n\n        age: a\n\n      default operator ?\n      default operator $$\n\n  defines program\n\n    JsonToJsonDemo()\n      stdout <- Stdout()\n\n      // === $$ ON BUILT-IN TYPES ===\n\n      jsonInt <- $$ 42\n      stdout.println(`Integer as JSON: ${jsonInt}`)\n\n      jsonStr <- $$ \"hello\"\n      stdout.println(`String as JSON: ${jsonStr}`)\n\n      jsonBool <- $$ true\n      stdout.println(`Boolean as JSON: ${jsonBool}`)\n\n      // === $$ ON RECORDS ===\n\n      person <- Person(\"Alice\", 30)\n      jsonPerson <- $$ person\n      stdout.println(`Person as JSON: ${jsonPerson}`)\n\n      // === UNSET FIELDS BECOME NULL ===\n\n      partial <- Person()\n      jsonPartial <- $$ partial\n      stdout.println(`Partial person JSON: ${jsonPartial}`)","migrationContext":"Java: Jackson @JsonSerialize or Gson.toJson() for serialization. Python: json.dumps() with custom encoder. Rust: serde_json::to_string(). Go: json.Marshal(). Kotlin: kotlinx.serialization @Serializable. EK9: 'default operator $$' on records for automatic JSON serialization, $$ operator on any value.","keywords":["class","convert","data","field","json","null","operator","record","serialize","unset"],"primaryTopics":[],"typicalErrors":[{"error":"E08180","correct":"name as String: String()","incorrect":"name as String","explanation":"Record fields must be initialized inline with a default value. Declaring a field without an initializer triggers E08180. Use 'String()' for an unset default or provide a literal default value. See ek9 -h E08180 for details."},{"error":"E50060","correct":"jsonPerson <- $$ person","incorrect":"jsonPerson <- person.toJSON()","explanation":"EK9 types do not have a 'toJSON()' method. Use the $$ operator to convert any value to its JSON representation. For records, 'default operator $$' auto-generates JSON serialization of all fields. See ek9 -h E50060 for details."}],"companions":[]}
{"id":190,"category":"JSON and Data Processing","question":"How do I parse a JSON string in EK9?","url":"https://ek9.io/qa/QA0190.html","alternatePhrasings":["How do I create JSON from a string in EK9?","How do I access JSON properties in EK9?","How do I read JSON data in EK9?"],"answer":"Pass a JSON string to the JSON constructor to parse it. Access properties with .get(key) for objects and .get(index) for arrays. Iterate with .iterator().\n\nPARSING JSON STRINGS\nPass a JSON-formatted string to the JSON constructor:\n  data <- JSON(`{\"name\": \"Alice\", \"age\": 30}`)\n\nACCESSING PROPERTIES\nUse .get(key) to access object properties:\n  name <- parsed.get(\"name\")\nUse .get(index) to access array elements:\n  first <- jsonArray.get(0)\n\nNATURE CHECKS\nCheck what kind of JSON value you have:\n  parsed.objectNature()  true for JSON objects\n  parsed.arrayNature()   true for JSON arrays\n  parsed.valueNature()   true for primitives\n\nITERATION\nIterate over JSON arrays or object entries:\n  for item in parsed.iterator()\n    stdout.println($item)\n\nSee Q188 for JSON as a first-class type. See Q48 for Result type. See Q29 for unset variables.","ek9Example":"defines module qa.jsonparsed.parsing\n\n  defines program\n\n    JsonParsingDemo()\n      stdout <- Stdout()\n\n      // === PARSING JSON STRINGS ===\n\n      parsed <- JSON(`{\"name\": \"Alice\", \"age\": 30}`)\n      stdout.println(`Parsed JSON: ${parsed}`)\n\n      // === ACCESSING PROPERTIES ===\n\n      nameJson <- parsed.get(\"name\")\n      stdout.println(`Name: ${nameJson}`)\n\n      ageJson <- parsed.get(\"age\")\n      stdout.println(`Age: ${ageJson}`)\n\n      // === NATURE CHECKS ===\n\n      stdout.println(`Is object: ${parsed.objectNature()}`)\n      stdout.println(`Is value: ${parsed.valueNature()}`)\n\n      // === JSON ARRAYS ===\n\n      arr <- JSON(`[10, 20, 30]`)\n      stdout.println(`Array: ${arr}`)\n      stdout.println(`Is array: ${arr.arrayNature()}`)\n\n      firstItem <- arr.get(0)\n      stdout.println(`First item: ${firstItem}`)\n\n      // === ITERATION ===\n\n      for item in arr.iterator()\n        stdout.println(`Item: ${item}`)","migrationContext":"Java: Jackson ObjectMapper.readTree() for tree model, readValue() for POJO binding. Python: json.loads() to dict. Rust: serde_json::from_str(). Go: json.Unmarshal(). Kotlin: kotlinx.serialization Json.decodeFromString(). EK9: JSON constructor parses strings directly, .get() for property access, .iterator() for traversal.","keywords":["access","data","error","get","guard","iterate","json","null","ok","parse","property","result","serialize","string"],"primaryTopics":["parse JSON","JSON parse","deserialize JSON"],"typicalErrors":[{"error":"E50060","correct":"nameJson <- parsed.get(\"name\")","incorrect":"nameJson <- parsed.getString(\"name\")","explanation":"The JSON type does not have 'getString()', 'getInt()', or similar typed accessor methods. Use '.get(key)' for object properties and '.get(index)' for array elements. The result is always a JSON value. See ek9 -h E50060 for details."}],"companions":[]}
{"id":191,"category":"JSON and Data Processing","question":"How do I process JSON data with stream pipelines in EK9?","url":"https://ek9.io/qa/QA0191.html","alternatePhrasings":["How do I extract typed values from JSON using streams?","How do I sum JSON values with pipelines in EK9?","How does collect as work with JSON in EK9?"],"answer":"EK9 stream pipelines can extract typed values from JSON using 'collect as Type'. JSON values flow through pipelines and convert to target types automatically.\n\nJSON TO TYPED VALUE\nExtract a single typed value from JSON:\n  json <- List(JSON(\"val\", JSON(2)))\n  anInt <- cat json | collect as Integer\nThe pipeline extracts the integer value 2.\n\nJSON ARRAY SUMMATION\nConvert a list to JSON and sum via pipeline:\n  total <- cat $$ [4, 6, 18, 22] | collect as Integer\nThe $$ converts the list to JSON, pipeline sums as Integer.\n\nJSON OBJECT PROPERTY SUMMATION\nParse a JSON object and sum numeric properties:\n  obj <- JSON(`{\"a\": 1, \"b\": null, \"c\": 22}`)\n  total <- cat obj | collect as Integer\nNull values are skipped automatically (become unset in tri-state model).\n\nNULL HANDLING\nJSON null becomes an unset value in EK9:\n  json <- List(JSON(\"val\", JSON(\"null\")))\n  result <- cat json | collect as Integer\n  // result is unset (not result?)\n\nSee Q188 for JSON basics. See Q59 for function pipelines. See Q122 for collect as.","ek9Example":"defines module qa.jsondata.streams\n\n  defines program\n\n    JsonStreamsDemo()\n      stdout <- Stdout()\n\n      // === JSON TO TYPED VALUE ===\n\n      json <- List(JSON(\"mainValue\", JSON(2)))\n      anInt <- cat json | collect as Integer\n      stdout.println(`Extracted integer: ${anInt}`)\n\n      // === JSON ARRAY SUMMATION ===\n\n      sumOfArray <- cat $$ [4, 6, 18, 22] | collect as Integer\n      stdout.println(`Sum of array: ${sumOfArray}`)\n\n      // === JSON OBJECT PROPERTY SUMMATION ===\n\n      jsonObject <- JSON(`{\"first\": 1, \"second\": null, \"third\": 22}`)\n      sumOfProps <- cat jsonObject | collect as Integer\n      stdout.println(`Sum of properties: ${sumOfProps}`)\n\n      // === NULL HANDLING ===\n\n      // JSON null becomes unset in EK9's tri-state model\n      jsonWithNull <- List(JSON(\"mainValue\", JSON(\"null\")))\n      nullResult <- cat jsonWithNull | collect as Integer\n      if not nullResult?\n        stdout.println(\"JSON null became unset Integer\")\n\n      // === FLOAT EXTRACTION ===\n\n      jsonFloat <- List(JSON(2.88))\n      aFloat <- cat jsonFloat | collect as Float\n      stdout.println(`Extracted float: ${aFloat}`)","migrationContext":"Java: Jackson + Stream API for JSON processing, manual type extraction. Python: json.loads() + list comprehensions. Rust: serde_json + iterators. Go: json.Unmarshal to struct + range loops. EK9: JSON integrates with stream pipelines natively, 'collect as Type' extracts and aggregates typed values from JSON.","keywords":["aggregate","collect","convert","data","extract","json","pipeline","serialize","stream","sum","type"],"primaryTopics":[],"typicalErrors":[{"error":"E50001","correct":"jsonObject <- JSON(`{\"first\": 1, \"second\": null, \"third\": 22}`)","incorrect":"jsonObject <- JSON.parse(`{\"first\": 1, \"second\": null, \"third\": 22}`)","explanation":"The JSON type does not have a static 'parse()' method. Pass the JSON string directly to the JSON constructor. The constructor handles parsing automatically. See ek9 -h E50001 for details."}],"companions":[]}
{"id":192,"category":"JSON and Data Processing","question":"How do I modify and combine JSON objects in EK9?","url":"https://ek9.io/qa/QA0192.html","alternatePhrasings":["How do I merge two JSON objects in EK9?","How do I build a JSON object from parts in EK9?","How do I add properties to JSON in EK9?"],"answer":"EK9 provides operators for building and combining JSON objects. Use constructors to create objects and operators to merge, replace, or add properties.\n\nBUILDING JSON OBJECTS\nCreate an empty JSON object:\n  obj <- JSON().object()\nCreate with key-value pairs:\n  person <- JSON(\"name\", JSON(\"Alice\"))\n\nADDING PROPERTIES (+)\nAdd a key-value pair to a JSON object:\n  person + JSON(\"age\", JSON(30))\n\nMERGE OPERATOR (:~:)\nMerge properties from one JSON object into another:\n  base :~: extras\nExisting properties in base are kept, new properties from extras are added.\n\nREPLACE OPERATOR (:^:)\nReplace matching properties:\n  target :^: replacement\nProperties in target that also exist in replacement are overwritten.\n\nSee Q188 for JSON basics. See Q96 for operator semantics. See Q189 for converting to JSON.","ek9Example":"defines module qa.jsondata.manipulate\n\n  defines program\n\n    JsonManipulateDemo()\n      stdout <- Stdout()\n\n      // === BUILDING JSON OBJECTS ===\n\n      emptyObj <- JSON().object()\n      stdout.println(`Empty object: ${emptyObj}`)\n\n      person <- JSON(\"name\", JSON(\"Alice\"))\n      stdout.println(`Person: ${person}`)\n\n      // === ADDING PROPERTIES WITH + ===\n\n      updated <- person + JSON(\"age\", JSON(30))\n      stdout.println(`With age: ${updated}`)\n\n      // === MERGE OPERATOR :~: ===\n\n      base <- JSON(`{\"name\": \"Alice\", \"role\": \"dev\"}`)\n      extras <- JSON(`{\"team\": \"backend\", \"level\": 5}`)\n      base :~: extras\n      stdout.println(`After merge: ${base}`)\n\n      // === REPLACE OPERATOR :^: ===\n\n      target <- JSON(`{\"name\": \"Alice\", \"age\": 30}`)\n      replacement <- JSON(`{\"age\": 31, \"city\": \"London\"}`)\n      target :^: replacement\n      stdout.println(`After replace: ${target}`)","migrationContext":"Java: Jackson ObjectNode.put()/set() for building, JsonNode merge via custom code. Python: dict.update() or {**a, **b} merge. Rust: serde_json::Map insert/extend. Go: manual map merging. Kotlin: mutableMapOf + putAll. EK9: :~: merge and :^: replace operators on JSON, + for adding properties.","keywords":["add","build","combine","data","json","manipulate","merge","object","property","replace","serialize"],"primaryTopics":[],"typicalErrors":[{"error":"E50001","correct":"emptyObj <- JSON().object()","incorrect":"emptyObj <- JSON.createObject()","explanation":"The JSON type does not have a static 'createObject()' method. Create an empty JSON object by calling 'JSON().object()' which creates an unset JSON and then converts it to an empty object. See ek9 -h E50001 for details."}],"companions":[]}
{"id":193,"category":"JSON and Data Processing","question":"How do I format JSON output in EK9?","url":"https://ek9.io/qa/QA0193.html","alternatePhrasings":["How do I pretty print JSON in EK9?","How do I get readable JSON output in EK9?","What is the difference between compact and formatted JSON in EK9?"],"answer":"EK9 provides both compact and formatted JSON output. Use the $ operator for compact output and .prettyPrint() for human-readable formatted output.\n\nCOMPACT OUTPUT ($)\nThe $ string operator produces compact JSON:\n  text <- $jsonObj\nNo extra whitespace, suitable for APIs and storage.\n\nPRETTY PRINT\nThe .prettyPrint() method produces indented, readable JSON:\n  formatted <- jsonObj.prettyPrint()\nUseful for debugging, logging, and configuration files.\n\nSTRING INTERPOLATION\nUse JSON values directly in string interpolation:\n  stdout.println(`Data: ${jsonObj}`)\nThis calls the $ operator automatically.\n\nSee Q188 for JSON as a first-class type. See Q43 for string interpolation.","ek9Example":"defines module qa.jsondata.output\n\n  defines program\n\n    JsonOutputDemo()\n      stdout <- Stdout()\n\n      jsonData <- JSON(`{\"name\": \"Alice\", \"age\": 30, \"active\": true}`)\n\n      // === COMPACT OUTPUT WITH $ ===\n\n      compact <- $jsonData\n      stdout.println(\"Compact: \" + compact)\n\n      // === PRETTY PRINT ===\n\n      formatted <- jsonData.prettyPrint()\n      stdout.println(\"Formatted:\")\n      stdout.println(formatted)\n\n      // === STRING INTERPOLATION ===\n\n      stdout.println(`Interpolated: ${jsonData}`)\n\n      // === NESTED JSON ===\n\n      nested <- JSON(`{\"person\": {\"name\": \"Bob\", \"scores\": [90, 85, 92]}}`)\n      stdout.println(\"Nested compact: \" + $nested)\n      stdout.println(\"Nested formatted:\")\n      stdout.println(nested.prettyPrint())","migrationContext":"Java: Jackson ObjectMapper.writerWithDefaultPrettyPrinter(). Python: json.dumps(indent=2). Rust: serde_json::to_string_pretty(). Go: json.MarshalIndent(). Kotlin: Json { prettyPrint = true }. EK9: .prettyPrint() method on JSON type, $ operator for compact form.","keywords":["compact","data","display","format","json","output","pretty","print","readable","serialize","string"],"primaryTopics":[],"typicalErrors":[{"error":"E50060","correct":"compact <- $jsonData","incorrect":"compact <- jsonData.toString()","explanation":"The JSON type does not have a 'toString()' method. Use the '$' operator for compact string conversion and '.prettyPrint()' for formatted output. EK9 uses operator syntax, not Java-style method calls. See ek9 -h E50060 for details."}],"companions":[]}
{"id":194,"category":"Generics","question":"How do I define a generic class in EK9?","url":"https://ek9.io/qa/QA0194.html","alternatePhrasings":["How do generic types work in EK9?","How do I create a parameterized class in EK9?","What is the syntax for generics in EK9?"],"answer":"EK9 uses natural-language syntax for generics with 'of type T'. Generic classes define type parameters that are filled in when the class is instantiated.\n\nDEFINING A GENERIC CLASS\nUse 'of type T' after the class name:\n  Container of type T\n    item as T?\nT is the type parameter that gets replaced with a real type.\n\nCONSTRUCTOR WITH TYPE PARAMETER\nConstructors accept T-typed arguments:\n  Container()\n    -> val as T\n    item :=? val\n\nMETHODS RETURNING T\nMethods can return the type parameter:\n  get()\n    <- rtn as T: T()\n\nINSTANTIATION WITH TYPE INFERENCE\nThe type parameter is inferred from the constructor argument:\n  strContainer <- Container(\"hello\")\nOr explicitly specified:\n  intContainer <- Container() of Integer\n\nSee Q58 for generic functions. See Q93 for class basics. See Q101 for closed by default. See Q195 for type constraints. See Q196 for multi-parameter generics. See Q197 for extending generics. See Q198 for built-in generics.\n\nSee Q642 for generic constructor inference. See Q653 for type independence.","ek9Example":"defines module qa.generics.classes\n\n  defines class\n\n    Container of type T\n      item as T?\n\n      Container() as pure\n        item :=? T()\n\n      Container() as pure\n        -> initialItem as T\n        item :=? initialItem\n\n      get()\n        <- rtn as T: T()\n        rtn :=? T(item)\n\n      default operator ?\n\n  defines program\n\n    GenericClassDemo()\n      stdout <- Stdout()\n\n      // === INSTANTIATION WITH TYPE INFERENCE ===\n\n      strContainer <- Container(\"hello\")\n      stdout.println(`String container set: ${strContainer?}`)\n\n      // === EXPLICIT TYPE SPECIFICATION ===\n\n      intContainer <- Container(42)\n      stdout.println(`Integer container set: ${intContainer?}`)\n\n      // === DEFAULT CONSTRUCTION ===\n\n      emptyContainer <- Container() of String\n      stdout.println(`Empty container set: ${emptyContainer?}`)\n\n      // === ACCESS VALUE ===\n\n      retrieved <- strContainer.get()\n      if retrieved?\n        stdout.println(`Retrieved: ${retrieved}`)","migrationContext":"Java: class Container<T> with angle brackets, type erasure at runtime. C++: template<typename T> with full monomorphization. Rust: struct Container<T> with trait bounds. Go: type Container[T any] (Go 1.18+). Kotlin: class Container<T> similar to Java. EK9: 'Container of type T' reads as natural English, type inference from constructors.","keywords":["class","container","define","generic","inference","instantiate","parameter","parameterized","type","type-parameter"],"primaryTopics":["generic class","generics","type parameter","template"],"typicalErrors":[{"error":"E06020","correct":"emptyContainer <- Container() of String","incorrect":"emptyContainer <- Container() of (String, Integer)","explanation":"Container has one type parameter T, so providing two type arguments is wrong. The number of type arguments must match the number of type parameters. See ek9 -h E06020 for details."}],"companions":[],"oracleToolHint":{"tool":"ek9_scaffold","intent":"class","description":"Oracle can generate a generic class outline with type parameters and constrained methods."}}
{"id":195,"category":"Generics","question":"How do I constrain generic type parameters in EK9?","url":"https://ek9.io/qa/QA0195.html","alternatePhrasings":["How do I restrict what types can be used with a generic in EK9?","What is 'constrain by' in EK9 generics?","How do I add type bounds to generics in EK9?"],"answer":"EK9 uses 'constrain by' to restrict generic type parameters to subtypes of a specific class. This enables safe access to methods of the constraining type.\n\nCONSTRAIN BY SYNTAX\nRestrict T to subtypes of a base type:\n  Handler of type T constrain by Animal\nNow T is guaranteed to have all Animal methods.\n\nACCESSING CONSTRAINED METHODS\nInside the generic, T's methods from Animal are accessible:\n  process()\n    -> item as T\n    name <- item.name()\nBecause T must be an Animal, calling name() is safe.\n\nCOMPILE-TIME SAFETY\nUsing an incompatible type is a compile error:\n  Handler of String  // ERROR: String is not an Animal\nOnly Animal subtypes are accepted.\n\nFUNCTION CONSTRAINTS\nGeneric functions also support constraints:\n  describe() of type T constrain by Animal\n    -> item as T\n    <- rtn as String: item.name()\n\nSee Q194 for generic classes. See Q58 for generic functions. See Q103 for abstract classes. See Q257 for constrained types (constrain as vs constrain by).","ek9Example":"defines module qa.generics.constraints\n\n  defines class\n\n    Animal as abstract\n      animalName as String?\n\n      default private Animal() as pure\n\n      Animal() as pure\n        -> n as String\n        animalName :=? String(n)\n\n      name() as pure\n        <- rtn as String: String(animalName)\n\n      override operator ? as pure\n        <- rtn as Boolean: animalName?\n\n    Dog is Animal\n\n      Dog() as pure\n        -> n as String\n        super(n)\n\n      Dog() as pure\n        super(\"Rex\")\n\n      bark() as pure\n        <- rtn as String: \"Woof!\"\n\n    Cat is Animal\n\n      Cat() as pure\n        -> n as String\n        super(n)\n\n      Cat() as pure\n        super(\"Whiskers\")\n\n      purr() as pure\n        <- rtn as String: \"Purr...\"\n\n  defines function\n\n    // Generic function constrained to Animal subtypes\n    describeAnimal() of type T constrain by Animal as open\n      -> item as T\n      <- rtn as String: item.name()\n\n  defines program\n\n    GenericConstraintsDemo()\n      stdout <- Stdout()\n\n      // === CONSTRAINED GENERIC FUNCTION ===\n\n      dog <- Dog()\n      dogDesc <- describeAnimal(dog)\n      stdout.println(`Dog: ${dogDesc}`)\n\n      theCat <- Cat()\n      catDesc <- describeAnimal(theCat)\n      stdout.println(`Cat: ${catDesc}`)\n\n      // The following would NOT compile:\n      // describeAnimal(\"hello\")  // String is not an Animal\n\n      stdout.println(\"Type constraints ensure only Animal subtypes are accepted\")","migrationContext":"Java: <T extends Animal> for upper bounds. C#: where T : Animal. Rust: <T: Animal> trait bounds. Go: type T Animal constraint (Go 1.18+). Kotlin: <T : Animal> bounds. EK9: 'of type T constrain by Animal' reads as natural English, same syntax for functions and classes.","keywords":["bound","constant","constrain","constraint","extends","generic","parameterized","restrict","safety","subtype","type","type-parameter"],"primaryTopics":["generic constraint","type constraint","bounded type"],"typicalErrors":[{"error":"E50060","correct":"dogDesc <- describeAnimal(dog)","incorrect":"dogDesc <- dog.describe()","explanation":"The method 'describe()' does not exist on Dog. Use the generic function 'describeAnimal()' which works on any Animal subtype. See ek9 -h E50060 for details."}],"companions":[]}
{"id":196,"category":"Generics","question":"How do I define generics with multiple type parameters in EK9?","url":"https://ek9.io/qa/QA0196.html","alternatePhrasings":["Can EK9 generics have more than one type parameter?","How do I create a Pair type with two generic parameters?","What is the syntax for multi-parameter generics in EK9?"],"answer":"EK9 supports multiple type parameters using parentheses: 'of type (A, B)'. This enables types like Pair, Either, and mapper functions with distinct input/output types.\n\nMULTIPLE TYPE PARAMETERS\nUse parentheses for multiple parameters:\n  Pair of type (A, B)\n    first as A?\n    second as B?\n\nINSTANTIATION\nProvide both types explicitly:\n  pair <- Pair(\"hello\", 42) of (String, Integer)\nOr let EK9 infer them:\n  pair <- Pair(\"hello\", 42)\n\nGENERIC FUNCTIONS WITH MULTIPLE PARAMETERS\nFunctions can also use multiple type parameters:\n  mapper() of type (S, T) as open\n    -> source as S\n    <- target as T?\n\nBUILT-IN EXAMPLES\nDict, Result, and DictEntry all use multiple type parameters:\n  Dict of (String, Integer)\n  Result of (String, Integer)\n\nSee Q194 for single-parameter generics. See Q195 for constraints. See Q46 for Dict.\n\nSee Q654 for parameter count validation. See Q655 for method overloading with generics.","ek9Example":"defines module qa.generics.multitype\n\n  defines class\n\n    Pair of type (A, B)\n      first as A?\n      second as B?\n\n      Pair() as pure\n        first :=? A()\n        second :=? B()\n\n      Pair() as pure\n        ->\n          a as A\n          b as B\n        first :=? a\n        second :=? b\n\n      getFirst()\n        <- rtn as A: A()\n        rtn :=? A(first)\n\n      getSecond()\n        <- rtn as B: B()\n        rtn :=? B(second)\n\n      default operator ?\n\n  defines program\n\n    MultiParamGenericDemo()\n      stdout <- Stdout()\n\n      // === INSTANTIATION WITH INFERENCE ===\n\n      pair <- Pair(\"hello\", 42)\n      stdout.println(`Pair is set: ${pair?}`)\n\n      // === ACCESSING VALUES ===\n\n      first <- pair.getFirst()\n      if first?\n        stdout.println(`First: ${first}`)\n\n      second <- pair.getSecond()\n      if second?\n        stdout.println(`Second: ${second}`)\n\n      // === DIFFERENT TYPE COMBINATIONS ===\n\n      boolFloat <- Pair(true, 3.14)\n      stdout.println(`Bool-Float pair set: ${boolFloat?}`)","migrationContext":"Java: class Pair<A, B> with angle brackets. C++: template<typename A, typename B>. Rust: struct Pair<A, B>. Go: type Pair[A, B any]. Kotlin: class Pair<A, B>. EK9: 'Pair of type (A, B)' with parentheses for multiple parameters, reads as natural English.","keywords":["define","generic","multi","multiple","pair","parameter","parameterized","parentheses","tuple","type","type-parameter"],"primaryTopics":[],"typicalErrors":[{"error":"E06220","correct":"pair <- Pair(\"hello\", 42)","incorrect":"pair <- Pair(\"hello\") of String","explanation":"Pair requires two type parameters (A, B). Providing only one type argument does not match the generic definition. See ek9 -h E06220 for details."},{"error":"E06030","correct":"a as A\n          b as B","incorrect":"a as A","explanation":"A generic class constructor parameter count must match the number of type parameters. Pair of type (A, B) needs a constructor accepting both A and B. See ek9 -h E06030 for details."}],"companions":[],"oracleToolHint":{"tool":"ek9_scaffold","intent":"class","description":"Oracle can generate a generic class with multiple type parameters."}}
{"id":197,"category":"Generics","question":"How do I extend or specialize a generic type in EK9?","url":"https://ek9.io/qa/QA0197.html","alternatePhrasings":["How do I create a concrete type from a generic in EK9?","Can I extend a parameterized type in EK9?","How does generic specialization work in EK9?"],"answer":"EK9 allows extending generic types that are marked 'as open'. You create concrete specializations by providing specific type arguments.\n\nOPEN GENERIC TYPES\nMark a generic type as open to allow extension:\n  BaseList of type T as open\n    items as List of T?\n\nSPECIALIZATION VIA EXTENSION\nCreate a concrete type by extending with specific types:\n  NameList is BaseList of String\nNameList is now a concrete class with String-typed items.\n\nADDING METHODS\nSpecializations can add new methods:\n  NameList is BaseList of String\n    findByPrefix()\n      -> prefix as String\n      <- rtn as String?\n\nGENERIC FUNCTION INSTANTIATION\nGeneric functions use 'is' or 'extends' for instantiation:\n  intCompare <- () is compareFunc of Integer as function\n\nSee Q194 for generic classes. See Q101 for closed by default. See Q102 for as open. See Q58 for generic functions.","ek9Example":"defines module qa.generics.extending\n\n  defines function\n\n    compareFunc() of type T as open\n      ->\n        item1 as T\n        item2 as T\n      <-\n        rtn as Integer := item1 <=> item2\n\n  defines class\n\n    BaseList of type T as open\n      items as List of T?\n\n      BaseList()\n        items :=? List() of T\n\n      BaseList()\n        -> item as T\n        items: List() of T\n        items += item\n\n      addItem()\n        -> item as T\n        if not items?\n          items: List() of T\n        items += item\n\n      count() as pure\n        <- rtn as Integer: 0\n        if items?\n          rtn: length items\n\n      default operator ?\n\n    // Specialization: concrete type from generic\n    NameList is BaseList of String\n\n      default operator ?\n\n  defines program\n\n    ExtendingGenericsDemo()\n      stdout <- Stdout()\n\n      // === GENERIC FUNCTION INSTANTIATION ===\n\n      intCompare <- () is compareFunc of Integer as function\n      result <- intCompare(10, 20)\n      stdout.println(`Compare 10 vs 20: ${result}`)\n\n      strCompare <- () extends compareFunc of String as function\n      strResult <- strCompare(\"apple\", \"banana\")\n      stdout.println(`Compare apple vs banana: ${strResult}`)\n\n      // === SPECIALIZED CLASS ===\n\n      names <- NameList()\n      names.addItem(\"Alice\")\n      names.addItem(\"Bob\")\n      stdout.println(`Name count: ${names.count()}`)","migrationContext":"Java: class NameList extends ArrayList<String>. C++: template specialization or typedef. Rust: type alias or newtype pattern. Go: type NameList = List[string] (no true specialization). Kotlin: class NameList : ArrayList<String>(). EK9: 'NameList is BaseList of String' with optional additional methods.","keywords":["concrete","extend","generic","inherit","open","parameterize","parameterized","specialize","type","type-parameter"],"primaryTopics":[],"typicalErrors":[{"error":"E50020","correct":"intCompare <- () is compareFunc of Integer as function","incorrect":"intCompare <- () is compareFunc of Integer as class","explanation":"Generic functions must be instantiated with 'as function', not 'as class'. Using the wrong construct type triggers E50020 — type not resolved. See ek9 -h E50020 for details."}],"companions":[]}
{"id":198,"category":"Generics","question":"What built-in generic types does EK9 provide?","url":"https://ek9.io/qa/QA0198.html","alternatePhrasings":["What parameterized types come with EK9?","Which generic containers does EK9 have?","Can I extend built-in generic types in EK9?"],"answer":"EK9 provides several built-in generic types. These are closed by default and cannot be extended. Use composition (wrapping) instead of inheritance.\n\nLIST OF T\nOrdered collection:\n  names <- List() of String\n  names += \"Alice\"\n\nDICT OF (K, V)\nKey-value mapping:\n  ages <- Dict() of (String, Integer)\n\nOPTIONAL OF T\nMay or may not contain a value:\n  maybe <- Optional(\"hello\")\n\nRESULT OF (OK, ERR)\nSuccess or error value:\n  result <- Result(42) of (Integer, String)\n\nITERATOR OF T\nLazy sequence of values from .iterator() on collections.\n\nMUTEXLOCK OF T\nThread-safe wrapper for concurrent access. Held as a FIELD on a wrapping class (locals are rejected by E08256). See Q158 for the canonical field-on-class pattern.\n\nCLOSED BY DEFAULT\nBuilt-in generics cannot be extended. Use composition:\n  MyList             // CORRECT: wrap a List\n    items as List of String\n\nSee Q45 for List. See Q46 for Dict. See Q47 for Optional. See Q48 for Result. See Q128 for closed collections. See Q158 for MutexLock.","ek9Example":"defines module qa.generics.builtin\n\n  defines program\n\n    BuiltinGenericsDemo()\n      stdout <- Stdout()\n\n      // === LIST OF T ===\n\n      names <- List() of String\n      names += \"Alice\"\n      names += \"Bob\"\n      stdout.println(`List size: ${length names}`)\n\n      // === DICT OF (K, V) ===\n\n      ages <- {\"Alice\": 30, \"Bob\": 25}\n      stdout.println(`Dict size: ${length ages}`)\n\n      // === OPTIONAL OF T ===\n\n      maybe <- Optional(\"hello\")\n      if maybe?\n        stdout.println(`Optional has value: ${maybe}`)\n\n      emptyOpt <- Optional() of String\n      stdout.println(`Empty optional set: ${emptyOpt?}`)\n\n      // === RESULT OF (OK, ERR) ===\n\n      okResult <- Result(\"Success\", Integer())\n      stdout.println(`Result is ok: ${okResult?}`)\n\n      // === MUTEXLOCK OF T ===\n      // MutexLock must be a FIELD on a wrapping class — see Q158 and Q213\n      // for the canonical pattern. Locals are rejected (E08256).\n\n      stdout.println(\"MutexLock of T — see Q158 for the field-on-class pattern\")\n\n      // === CANNOT EXTEND (CLOSED) ===\n      // MyList extends List of String  // ERROR: not open\n      // Use composition instead:\n      //   MyList\n      //     items as List of String\n\n      stdout.println(\"Built-in generics are closed, use composition\")","migrationContext":"Java: ArrayList<T>, HashMap<K,V>, Optional<T> are all open to extension. Python: list, dict are open. Rust: Vec<T>, HashMap<K,V> cannot be inherited (no inheritance). Go: slices and maps are built-in, no generics before 1.18. Kotlin: List<T>, Map<K,V> are interfaces. EK9: List, Dict, Optional, Result, MutexLock are closed generic types, use composition pattern.","keywords":["absent","built-in","dict","error","generic","guard","iterator","list","mutexlock","ok","optional","parameterized","priorityqueue","result","safe","type-parameter"],"primaryTopics":["built-in generics","List of","Dict of"],"typicalErrors":[{"error":"E06020","correct":"names <- List() of String","incorrect":"names <- List() of (String, Integer)","explanation":"List takes one type parameter T. Providing two type arguments does not match the generic definition. See ek9 -h E06020 for details."}],"companions":[]}
{"id":199,"category":"Web Services","question":"How do I create a REST GET endpoint in EK9?","url":"https://ek9.io/qa/QA0199.html","alternatePhrasings":["How do I define a service in EK9?","How do I build a web API endpoint in EK9?","How do I define a REST endpoint in EK9?"],"answer":"EK9 has a built-in 'defines service' construct for REST endpoints. Services declare URI paths and methods that map to HTTP operations.\n\nSERVICE DEFINITION\nDefine a service with a base URI path:\n  defines service\n    Info :/info\nThe service name 'Info' is bound to the '/info' path.\n\nGET METHOD\nNamed methods default to GET:\n  welcome() as GET for :/welcome\n    <- response as HTTPResponse: ...\n\nHTTPRESPONSE TRAIT\nReturn an HTTPResponse from service methods. Use a dynamic class with trait delegation:\n  () with trait HTTPResponse\n    override content()\n      <- rtn as String: \"Hello\"\n    override status() as pure\n      <- rtn as Integer: 200\n\nAPPLICATION REGISTRATION\nRegister services in an application:\n  defines application\n    MyApp\n      register Info()\n\nSee Q200 for CRUD operators. See Q201 for HTTP responses. See Q112 for full service example. See Q111 for components.\n\nSee Q657 for URI mapping. See Q659 for HTTPResponse.","ek9Example":"defines module qa.web.restget\n\n  defines text for \"en\"\n\n    WelcomeText\n      greeting()\n        \"Welcome to the EK9 Service\"\n\n  defines service\n\n    Info :/info open\n\n      welcome() as GET for :/welcome\n        <- response as HTTPResponse: () with trait HTTPResponse\n          text <- WelcomeText(\"en\")\n\n          override cacheControl() as pure\n            <- rtn as String: \"no-store\"\n          override contentType() as pure\n            <- rtn as String: \"text/plain\"\n          override contentLanguage() as pure\n            <- rtn as String: \"en\"\n          override content()\n            <- rtn as String: text.greeting()\n          override status() as pure\n            <- rtn as Integer: 200\n          default operator ?\n\n  defines application\n\n    WebApp\n      register Info()\n\n  defines program\n\n    RestGetDemo()\n      stdout <- Stdout()\n\n      stdout.println(\"Service registered with application\")\n      stdout.println(\"GET /info/welcome returns welcome text\")\n      stdout.println(\"HTTPResponse provides status, content, headers\")","migrationContext":"Java: JAX-RS @GET @Path or Spring @GetMapping. Python: Flask @app.route('/path'). Rust: actix-web or axum handler functions. Go: http.HandleFunc('/path', handler). Kotlin: Ktor routing DSL. EK9: language-level 'defines service' with :/path URIs, named methods default to GET.","keywords":["api","define","endpoint","get","http","path","response","rest","service","uri"],"primaryTopics":["REST","REST endpoint","GET endpoint","HTTP GET"],"typicalErrors":[{"error":"E50060","correct":"text <- WelcomeText(\"en\")","incorrect":"text <- WelcomeText(42)","explanation":"The WelcomeText text construct expects a String locale parameter. Passing an Integer triggers E50060 — constructor not resolved. See ek9 -h E50060 for details."}],"companions":[],"oracleToolHint":{"tool":"ek9_scaffold","intent":"service","description":"Oracle can generate a service with REST endpoint methods and path bindings."}}
{"id":200,"category":"Web Services","question":"How do EK9 operators map to HTTP methods for CRUD?","url":"https://ek9.io/qa/QA0200.html","alternatePhrasings":["How do I create POST, PUT, DELETE endpoints in EK9?","What operators map to HTTP methods in EK9 services?","How does EK9 map CRUD to service operators?"],"answer":"EK9 maps operators to HTTP methods semantically. This makes CRUD operations natural and consistent with the language's operator design.\n\nOPERATOR TO HTTP MAPPING\nNamed methods and operators map to HTTP verbs:\n  listAll() :/             GET (read)\n  operator += :/           POST (create/add)\n  operator -= :/{id}       DELETE (remove)\n  operator :~: :/{id}      PATCH (merge/partial update)\n  operator :^: :/{id}      PUT (replace)\n\nWHY OPERATORS\nThe mapping is semantic, not arbitrary:\n  += adds to a collection (POST creates a resource)\n  -= removes from a collection (DELETE removes a resource)\n  :~: merges into existing (PATCH partially updates)\n  :^: replaces entirely (PUT replaces completely)\n\nEACH RETURNS HTTPResponse\nAll CRUD methods return an HTTPResponse with status, content, and headers.\n\nSee Q199 for GET endpoints. See Q201 for HTTP responses. See Q202 for parameter binding. See Q96 for operator semantics.\n\nSee Q660 for CRUD operator patterns. See Q658 for path parameters.","ek9Example":"defines module qa.web.crud\n\n  defines service\n\n    Items :/items open\n\n      // GET /items — list all items\n      listAll() :/\n        <- response as HTTPResponse: () with trait HTTPResponse\n          override content()\n            <- rtn as String: `[\"item1\", \"item2\"]`\n          override status() as pure\n            <- rtn as Integer: 200\n          override contentType() as pure\n            <- rtn as String: \"application/json\"\n          override cacheControl() as pure\n            <- rtn as String: \"no-cache\"\n          override contentLanguage() as pure\n            <- rtn as String: \"en\"\n          default operator ?\n\n      // POST /items — add new item\n      operator += :/\n        -> content as String :=: CONTENT\n        <- response as HTTPResponse: () with trait HTTPResponse\n          override content()\n            <- rtn as String: `{\"created\": true}`\n          override status() as pure\n            <- rtn as Integer: 201\n          override contentType() as pure\n            <- rtn as String: \"application/json\"\n          override cacheControl() as pure\n            <- rtn as String: \"no-store\"\n          override contentLanguage() as pure\n            <- rtn as String: \"en\"\n          default operator ?\n\n      // DELETE /items/{id} — remove item\n      operator -= :/{id}\n        -> id as String\n        <- response as HTTPResponse: () with trait HTTPResponse\n          override content()\n            <- rtn as String: \"\"\n          override status() as pure\n            <- rtn as Integer: 204\n          override contentType() as pure\n            <- rtn as String: \"text/plain\"\n          override cacheControl() as pure\n            <- rtn as String: \"no-store\"\n          override contentLanguage() as pure\n            <- rtn as String: \"en\"\n          default operator ?\n\n  defines application\n\n    CrudApp\n      register Items()\n\n  defines program\n\n    CrudOperatorsDemo()\n      stdout <- Stdout()\n\n      stdout.println(\"CRUD operator mapping:\")\n      stdout.println(\"  += maps to POST (create)\")\n      stdout.println(\"  -= maps to DELETE (remove)\")\n      stdout.println(\"  :~: maps to PATCH (merge)\")\n      stdout.println(\"  :^: maps to PUT (replace)\")","migrationContext":"Java: JAX-RS @POST/@PUT/@DELETE annotations. Python: Flask methods=['POST','PUT','DELETE']. Rust: actix-web .post()/.put()/.delete(). Go: manual method checking in handler. Kotlin: Ktor post/put/delete route blocks. EK9: operators (+=, -=, :~:, :^:) map semantically to POST, DELETE, PATCH, PUT.","keywords":["crud","delete","dict","http","mapping","method","operator","patch","post","put","rest","service"],"primaryTopics":["CRUD","HTTP methods","POST PUT DELETE"],"typicalErrors":[{"error":"E50060","correct":"stdout.println(\"CRUD operator mapping:\")","incorrect":"stdout.display(\"CRUD operator mapping:\")","explanation":"Stdout does not have a display() method. The correct method is println(). Calling a non-existent method triggers E50060 — method not resolved. See ek9 -h E50060 for details."}],"companions":[]}
{"id":201,"category":"Web Services","question":"How do I create HTTP responses in EK9 services?","url":"https://ek9.io/qa/QA0201.html","alternatePhrasings":["How does the HTTPResponse trait work in EK9?","How do I set HTTP status codes in EK9 services?","How do I return JSON from an EK9 service?"],"answer":"Service methods return HTTPResponse, a trait with methods for content, status, headers, and caching. Use dynamic classes to implement it inline.\n\nHTTPRESPONSE TRAIT\nHTTPResponse defines these methods:\n  content()           the response body\n  status()            HTTP status code (200, 404, etc.)\n  contentType()       MIME type (application/json, etc.)\n  cacheControl()      cache directives\n  contentLanguage()   language tag\n\nDYNAMIC IMPLEMENTATION\nCreate inline with dynamic class syntax:\n  <- response as HTTPResponse: () with trait HTTPResponse\n    override content()\n      <- rtn as String: `{\"msg\": \"ok\"}`\n    override status() as pure\n      <- rtn as Integer: 200\n    override contentType() as pure\n      <- rtn as String: \"application/json\"\n    ...\n    default operator ?\n\nSTATUS CODES\nReturn appropriate HTTP status codes:\n  200 for success, 201 for created,\n  204 for no content, 404 for not found.\n\nSee Q199 for REST GET endpoints. See Q200 for CRUD operators. See Q115 for dynamic classes. See Q106 for traits.","ek9Example":"defines module qa.web.httpresponse\n\n  defines service\n\n    Health :/health open\n\n      check() as GET for :/status\n        <- response as HTTPResponse: () with trait HTTPResponse\n          override content()\n            <- rtn as String: `{\"status\": \"healthy\"}`\n          override status() as pure\n            <- rtn as Integer: 200\n          override contentType() as pure\n            <- rtn as String: \"application/json\"\n          override cacheControl() as pure\n            <- rtn as String: \"no-cache\"\n          override contentLanguage() as pure\n            <- rtn as String: \"en\"\n          default operator ?\n\n  defines application\n\n    HealthApp\n      register Health()\n\n  defines program\n\n    HttpResponseDemo()\n      stdout <- Stdout()\n\n      stdout.println(\"HTTPResponse trait methods:\")\n      stdout.println(\"  content() - response body\")\n      stdout.println(\"  status() - HTTP status code\")\n      stdout.println(\"  contentType() - MIME type\")\n      stdout.println(\"  cacheControl() - cache directive\")\n      stdout.println(\"  contentLanguage() - language tag\")","migrationContext":"Java: JAX-RS Response.ok().entity(body).build() or Spring ResponseEntity. Python: Flask make_response() or return tuple (body, status). Rust: axum IntoResponse trait. Go: w.WriteHeader(status) + w.Write(body). Kotlin: Ktor call.respond(). EK9: dynamic class implementing HTTPResponse trait inline.","keywords":["anonymous","cache","capture","closure","content","define","delegate","dynamic","http","response","rest","service","status","trait"],"primaryTopics":[],"typicalErrors":[{"error":"E50060","correct":"stdout.println(\"HTTPResponse trait methods:\")","incorrect":"stdout.display(\"HTTPResponse trait methods:\")","explanation":"Stdout does not have a display() method. The correct method is println(). Calling a non-existent method triggers E50060 — method not resolved. See ek9 -h E50060 for details."}],"companions":[],"oracleToolHint":{"tool":"ek9_scaffold","intent":"service","description":"Oracle can generate a service with HTTP response handling methods."}}
{"id":202,"category":"Web Services","question":"How do I bind path parameters and request body in EK9 services?","url":"https://ek9.io/qa/QA0202.html","alternatePhrasings":["How does automatic parameter binding work in EK9 services?","How do I access the request body in EK9?","How does parameter binding work in EK9 services?"],"answer":"EK9 services bind path parameters and request content automatically using URI templates and binding keywords.\n\nPATH PARAMETERS (IMPLICIT)\nPath segments in braces bind to parameters by name:\n  byId() as GET for :/{id}\n    -> id as String\nThe {id} in the path automatically binds to the id parameter.\n\nEXPLICIT PATH BINDING\nUse :=: PATH for explicit binding to a named path segment:\n  -> itemId as String :=: PATH \"item-id\"\nBinds parameter 'itemId' to the path segment named 'item-id'.\n\nCONTENT BINDING\nAccess the request body with :=: CONTENT:\n  -> content as String :=: CONTENT\nThe entire request body is bound to the parameter.\n\nREQUEST BINDING\nAccess the full request object with :=: REQUEST:\n  -> request as HTTPRequest :=: REQUEST\nProvides access to all request information.\n\nSee Q199 for REST GET endpoints. See Q200 for CRUD operators. See Q112 for full service example.","ek9Example":"defines module qa.web.parameters\n\n  defines service\n\n    Products :/products open\n\n      // Path parameter implicit binding\n      byId() as GET for :/{id}\n        -> id as String\n        <- response as HTTPResponse?\n\n        itemId <- String(id)\n        response: (itemId) with trait HTTPResponse\n          override content()\n            <- rtn as String: `{\"id\": \"${itemId}\"}`\n          override status() as pure\n            <- rtn as Integer: 200\n          override contentType() as pure\n            <- rtn as String: \"application/json\"\n          override cacheControl() as pure\n            <- rtn as String: \"no-cache\"\n          override contentLanguage() as pure\n            <- rtn as String: \"en\"\n          default operator ?\n\n      // Content binding for POST body\n      operator += :/\n        -> content as String :=: CONTENT\n        <- response as HTTPResponse: () with trait HTTPResponse\n          override content()\n            <- rtn as String: `{\"received\": true}`\n          override status() as pure\n            <- rtn as Integer: 201\n          override contentType() as pure\n            <- rtn as String: \"application/json\"\n          override cacheControl() as pure\n            <- rtn as String: \"no-store\"\n          override contentLanguage() as pure\n            <- rtn as String: \"en\"\n          default operator ?\n\n  defines application\n\n    ProductApp\n      register Products()\n\n  defines program\n\n    ParameterBindingDemo()\n      stdout <- Stdout()\n\n      stdout.println(\"Path parameter binding:\")\n      stdout.println(\"  -> id as String (implicit from {id})\")\n      stdout.println(\"Content binding:\")\n      stdout.println(\"  -> content as String :=: CONTENT\")\n      stdout.println(\"Request binding:\")\n      stdout.println(\"  -> request as HTTPRequest :=: REQUEST\")","migrationContext":"Java: JAX-RS @PathParam(\"id\"), @RequestBody. Python: Flask route('<id>') and request.json. Rust: axum Path(id) and Json(body) extractors. Go: mux.Vars(r)[\"id\"] and io.ReadAll(r.Body). Kotlin: Ktor call.parameters[\"id\"] and call.receive(). EK9: {id} in path auto-binds, :=: CONTENT for body, :=: REQUEST for full request.","keywords":["bind","body","content","extract","http","parameter","path","request","rest","service","uri"],"primaryTopics":[],"typicalErrors":[{"error":"E50060","correct":"stdout.println(\"Path parameter binding:\")","incorrect":"stdout.display(\"Path parameter binding:\")","explanation":"Stdout does not have a display() method. The correct method is println(). Calling a non-existent method triggers E50060 — method not resolved. See ek9 -h E50060 for details."}],"companions":[]}
{"id":203,"category":"Web Services","question":"How do I wire services and components together in an EK9 application?","url":"https://ek9.io/qa/QA0203.html","alternatePhrasings":["How does application registration work in EK9?","How do I connect services and components in EK9?","How do I use dependency injection with services in EK9?"],"answer":"EK9 applications wire services and components together using 'register' declarations. Programs access injected components with the '!' suffix.\n\nCOMPONENT DEFINITION\nDefine abstract and concrete components:\n  defines component\n    Repository as abstract\n      findAll() as abstract\n        <- rtn as List of String?\n    InMemoryRepo extends Repository\n      override findAll()\n        <- rtn <- List() of String\n\nAPPLICATION REGISTRATION\nRegister concrete components and services:\n  defines application\n    MyApp\n      register InMemoryRepo() as Repository\n      register ItemService()\n\nPROGRAM WITH APPLICATION\nLink a program to an application for injection:\n  MyProgram() with application of MyApp\n    repo as Repository!\nThe '!' suffix injects the registered Repository.\n\nWIRING ORDER\nThe application definition controls which implementations satisfy abstract dependencies. The compiler validates that all injection points can be satisfied.\n\nSee Q111 for component basics. See Q112 for service basics. See Q199 for REST endpoints. See Q114 for aspects. See Q227 for compile-time DI validation. See Q228 for registration ordering. See Q231 for program-application linking.","ek9Example":"defines module qa.web.application\n\n  defines component\n\n    Repository as abstract\n      findAll() as abstract\n        <- rtn as List of String?\n\n      default operator ?\n\n    InMemoryRepo extends Repository\n      override findAll()\n        <- rtn <- List() of String\n        rtn += \"item1\"\n        rtn += \"item2\"\n\n      default operator ?\n\n  defines service\n\n    ItemService :/items open\n\n      listAll() :/\n        <- response as HTTPResponse: () with trait HTTPResponse\n          override content()\n            <- rtn as String: `{\"items\": []}`\n          override status() as pure\n            <- rtn as Integer: 200\n          override contentType() as pure\n            <- rtn as String: \"application/json\"\n          override cacheControl() as pure\n            <- rtn as String: \"no-cache\"\n          override contentLanguage() as pure\n            <- rtn as String: \"en\"\n          default operator ?\n\n  defines application\n\n    MyApp\n      register InMemoryRepo() as Repository\n      register ItemService()\n\n  defines program\n\n    AppWiringDemo() with application of MyApp\n      stdout <- Stdout()\n\n      repo as Repository!\n\n      items <- repo.findAll()\n      for item in items\n        stdout.println(`Item: ${item}`)\n\n      stdout.println(\"Components and services wired via application\")","migrationContext":"Java: Spring @Configuration with @Bean definitions, @Autowired injection. Python: FastAPI Depends() or manual DI. Rust: no built-in DI. Go: manual wiring or wire codegen. Kotlin: Koin module { single { } } or Dagger @Module. EK9: 'register X() as Y' in application definition, '!' suffix for injection.","keywords":["application","component","compose","dependency","http","inject","register","rest","service","wire"],"primaryTopics":[],"typicalErrors":[{"error":"E50060","correct":"items <- repo.findAll()","incorrect":"items <- repo.getAll()","explanation":"Repository does not have a getAll() method. The correct method name is findAll(). Using the wrong method name triggers E50060 — method not resolved. See ek9 -h E50060 for details."}],"companions":[],"oracleToolHint":{"tool":"ek9_scaffold","intent":"application","description":"Oracle can generate an application block with component registrations and service wiring."}}
{"id":204,"category":"Testing","question":"How do I write black-box tests with expected output files in EK9?","url":"https://ek9.io/qa/QA0204.html","alternatePhrasings":["How does output-based testing work in EK9?","What is the expected_output.txt pattern in EK9?","How do I test program output in EK9?"],"answer":"EK9 black-box tests validate program behavior by comparing stdout output against an expected_output.txt companion file. No assertions needed.\n\nBLACK-BOX TEST PATTERN\nMark a program with @Test:\n  @Test\n  MyTestProgram()\n    stdout <- Stdout()\n    stdout.println(\"Expected output\")\n\nEXPECTED OUTPUT FILE\nCreate a companion expected_output.txt in the same directory:\n  Expected output\nThe test runner compares stdout against this file line by line.\n\nNO ASSERTIONS NEEDED\nThe output IS the test. Print values and let the file comparison verify correctness. This eliminates testing framework boilerplate.\n\nWHEN TO USE BLACK-BOX TESTS\nIdeal for: integration tests, output formatting, end-to-end workflows, and any test where verifying printed output is the goal.\n\nSee Q205 for parameterized tests. See Q155 for basic unit testing. See Q156 for assertions. See Q208 for dynamic output placeholders.","ek9Example":"defines module qa.testdeep.blackbox\n\n  defines program\n\n    // === BLACK-BOX TEST ===\n    // A @Test program prints to stdout.\n    // A companion expected_output.txt file\n    // contains the expected output.\n    // The test runner compares them automatically.\n\n    // Note: This file demonstrates the pattern.\n    // In a real test directory, you would have:\n    //   myTest/myTest.ek9\n    //   myTest/expected_output.txt\n\n    BlackBoxTestingDemo()\n      stdout <- Stdout()\n\n      stdout.println(\"Black-box testing pattern:\")\n      stdout.println(\"  1. Write a @Test program\")\n      stdout.println(\"  2. Print expected values to stdout\")\n      stdout.println(\"  3. Create expected_output.txt file\")\n      stdout.println(\"  4. Test runner compares output\")\n      stdout.println(\"No assertions needed\")","migrationContext":"Java: JUnit requires assertion methods and output stream capture. Python: pytest capsys fixture for output capture. Rust: custom output capture helpers. Go: testing.T with stdout redirection. EK9: @Test program with expected_output.txt companion file, no assertions or framework API needed.","keywords":["black-box","comparison","coverage","expected","file","output","stdout","test","verify"],"primaryTopics":[],"typicalErrors":[{"error":"E50010","correct":"stdout <- Stdout()\n\n      stdout.println(\"Black-box testing pattern:\")","incorrect":"stdout.println(\"Black-box testing pattern:\")\n      stdout <- Stdout()","explanation":"Variables must be declared before use. Using stdout before its declaration is a forward reference. See ek9 -h E50010 for details."}],"companions":[]}
{"id":205,"category":"Testing","question":"How do I write parameterized tests in EK9?","url":"https://ek9.io/qa/QA0205.html","alternatePhrasings":["How do I run the same test with different data in EK9?","How do data-driven tests work in EK9?","How do I pass parameters to EK9 tests?"],"answer":"EK9 parameterized tests accept typed parameters via standard program parameter syntax. Test data comes from external files.\n\nPARAMETERIZED TEST PROGRAM\nDeclare parameters with the -> syntax:\n  @Test\n  MyTest()\n    -> input as String\n    stdout <- Stdout()\n    stdout.println(\"Got: \" + input)\n\nTEST DATA FILES\nEach test case has:\n  commandline_arg_<N>.txt  - input parameters\n  expected_case_<N>.txt    - expected output\nWhere N is the case number (1, 2, 3, ...).\n\nTYPED PARAMETERS\nParameters are strongly typed. The compiler validates types at compile time, not at test runtime.\n\nMULTIPLE PARAMETERS\n  @Test\n  Calculator()\n    ->\n      a as Integer\n      b as Integer\n    stdout <- Stdout()\n    stdout.println($a + b)\n\nSee Q204 for black-box testing. See Q155 for basic testing. See Q2 for compile and run.","ek9Example":"defines module qa.testdeep.parameterized\n\n  defines program\n\n    // === PARAMETERIZED TEST PATTERN ===\n    // @Test programs accept parameters just like regular programs.\n    // Test data comes from external files:\n    //   commandline_arg_1.txt, expected_case_1.txt\n    //   commandline_arg_2.txt, expected_case_2.txt\n\n    ParameterizedTestDemo()\n      stdout <- Stdout()\n\n      stdout.println(\"Parameterized test pattern:\")\n      stdout.println(\"  1. @Test program with -> parameters\")\n      stdout.println(\"  2. commandline_arg_N.txt for inputs\")\n      stdout.println(\"  3. expected_case_N.txt for outputs\")\n      stdout.println(\"  4. Same test logic, different data\")\n      stdout.println(\"Parameters are strongly typed\")","migrationContext":"Java: JUnit @ParameterizedTest with @ValueSource/@CsvSource. Python: @pytest.mark.parametrize. Rust: custom macros or rstest crate. Go: table-driven tests with struct slices. Kotlin: JUnit parameterized or Kotest forAll. EK9: standard program parameters with commandline_arg/expected_case file pairs.","keywords":["case","commandline","coverage","data-driven","expected","generic","input","multiple","parameterized","test"],"primaryTopics":[],"typicalErrors":[{"error":"E50010","correct":"stdout <- Stdout()\n\n      stdout.println(\"Parameterized test pattern:\")","incorrect":"stdout.println(\"Parameterized test pattern:\")\n      stdout <- Stdout()","explanation":"Variables must be declared before use. Using stdout before declaring it with '<-' is a forward reference. See ek9 -h E50010 for details."}],"companions":[]}
{"id":206,"category":"Testing","question":"How do I measure test coverage in EK9?","url":"https://ek9.io/qa/QA0206.html","alternatePhrasings":["How does code coverage work in EK9?","What coverage modes does EK9 support?","How do I get a coverage report in EK9?"],"answer":"EK9 has built-in test coverage instrumentation. Use the -tC flag to enable coverage, with three modes and a configurable threshold.\n\nCOVERAGE FLAG\nRun tests with coverage:\n  ek9 -tC myTests.ek9\nOr: ek9 -t4 myTests.ek9 (JSON + coverage)\n\nCOVERAGE MODES\nThree instrumentation modes:\n  SET    - records whether each line was executed (default)\n  COUNT  - counts how many times each line ran\n  ATOMIC - thread-safe counting for concurrent tests\n\nTHRESHOLD\nThe default coverage threshold is 80%. Tests fail if coverage falls below this. Configure in package settings.\n\nOUTPUT FORMATS WITH COVERAGE\n  -t4  JSON output with coverage data\n  -t5  Verbose coverage breakdown\n  -t6  HTML dashboard with visual coverage\n\nSee Q207 for all output formats. See Q155 for basic testing. See Q157 for running tests. See Q321 for the quality report dashboard.","ek9Example":"defines module qa.testdeep.coverage\n\n  defines function\n\n    addNumbers() as pure\n      ->\n        a as Integer\n        b as Integer\n      <-\n        rtn as Integer: a + b\n\n    isPositive() as pure\n      -> n as Integer\n      <- rtn as Boolean: n > 0\n\n  defines program\n\n    CoverageDemo()\n      stdout <- Stdout()\n\n      // === FUNCTIONS TO COVER ===\n\n      result <- addNumbers(3, 4)\n      stdout.println(`3 + 4 = ${result}`)\n\n      positive <- isPositive(5)\n      stdout.println(`5 is positive: ${positive}`)\n\n      negative <- isPositive(-1)\n      stdout.println(`-1 is positive: ${negative}`)\n\n      // === COVERAGE MODES ===\n      // Run with: ek9 -tC myTests.ek9\n      // SET    - line executed yes/no\n      // COUNT  - execution count per line\n      // ATOMIC - thread-safe counting\n\n      stdout.println(\"Coverage: SET, COUNT, or ATOMIC modes\")\n      stdout.println(\"Default threshold: 80%\")\n      stdout.println(\"Formats: -t4 JSON, -t5 verbose, -t6 HTML\")","migrationContext":"Java: JaCoCo or Cobertura external tools. Python: coverage.py with pytest-cov. Rust: cargo-tarpaulin or llvm-cov. Go: go test -cover built-in. Kotlin: JaCoCo via Gradle. EK9: built-in coverage with SET/COUNT/ATOMIC modes, 80% default threshold, HTML dashboard.","keywords":["atomic","count","coverage","html","instrument","percent","report","set","test","threshold"],"primaryTopics":["test coverage","code coverage"],"typicalErrors":[{"error":"E08091","correct":"isPositive() as pure\n      -> n as Integer\n      <- rtn as Boolean: n > 0","incorrect":"isPositive() as pure\n      -> n as Integer\n      <- rtn as Boolean: true","explanation":"The parameter n is declared but never used in the function body. All parameters must be referenced in the implementation. See ek9 -h E08091 for details."}],"companions":[]}
{"id":207,"category":"Testing","question":"What test output formats does EK9 support for CI/CD?","url":"https://ek9.io/qa/QA0207.html","alternatePhrasings":["How do I get JUnit XML output from EK9 tests?","What test report formats does EK9 support?","How do I integrate EK9 tests with CI pipelines?"],"answer":"EK9 supports 7 output formats via the -t flag, from terse to full HTML dashboards with coverage.\n\nOUTPUT FORMATS\n  -t0  Terse - pass/fail only, minimal output\n  -t1  Human-readable - formatted for developers\n  -t2  JSON - machine-readable test results\n  -t3  JUnit XML - standard CI/CD format\n  -t4  JSON with coverage data\n  -t5  Verbose coverage breakdown\n  -t6  HTML dashboard with visual coverage\n\nCI/CD INTEGRATION\nUse -t3 for Jenkins, GitHub Actions, GitLab CI:\n  ek9 -t3 myTests.ek9\nProduces standard JUnit XML that CI tools understand.\n\nJSON FOR CUSTOM TOOLS\nUse -t2 or -t4 for custom reporting pipelines:\n  ek9 -t2 myTests.ek9\n\nHTML DASHBOARD\nUse -t6 for development review:\n  ek9 -t6 myTests.ek9\nGenerates an interactive coverage dashboard.\n\nSee Q206 for coverage modes. See Q155 for basic testing. See Q157 for running tests. See Q321 for the HTML quality dashboard. See Q322 for profiling data. See Q627 for profiling output formats.","ek9Example":"defines module qa.testdeep.formats\n\n  defines program\n\n    TestOutputFormatsDemo()\n      stdout <- Stdout()\n\n      // === 7 OUTPUT FORMATS ===\n\n      stdout.println(\"EK9 test output formats:\")\n      stdout.println(\"  -t0  Terse (pass/fail only)\")\n      stdout.println(\"  -t1  Human-readable\")\n      stdout.println(\"  -t2  JSON results\")\n      stdout.println(\"  -t3  JUnit XML (CI/CD)\")\n      stdout.println(\"  -t4  JSON with coverage\")\n      stdout.println(\"  -t5  Verbose coverage\")\n      stdout.println(\"  -t6  HTML dashboard\")\n\n      // === CI/CD USAGE ===\n\n      stdout.println(\"CI/CD: ek9 -t3 myTests.ek9\")\n      stdout.println(\"Development: ek9 -t6 myTests.ek9\")","migrationContext":"Java: JUnit XML via Maven Surefire, JaCoCo for coverage HTML. Python: pytest --junitxml and coverage html. Rust: cargo test with custom formatters. Go: go test -json, gotestsum for formatting. Kotlin: JUnit XML via Gradle. EK9: 7 built-in formats from -t0 to -t6, no external tools needed.","keywords":["ci","coverage","format","html","json","junit","output","pipeline","terse","test","verbose","xml"],"primaryTopics":[],"typicalErrors":[{"error":"E50010","correct":"stdout <- Stdout()\n\n      // === 7 OUTPUT FORMATS ===\n\n      stdout.println(\"EK9 test output formats:\")","incorrect":"stdout.println(\"EK9 test output formats:\")\n      stdout <- Stdout()","explanation":"Variables must be declared before use. Using stdout before its declaration is a forward reference. See ek9 -h E50010 for details."}],"companions":[]}
{"id":208,"category":"Testing","question":"How do I test output containing dynamic values like dates, GUIDs, or timestamps?","url":"https://ek9.io/qa/QA0208.html","alternatePhrasings":["How do I handle non-deterministic output in EK9 tests?","What placeholders can I use in expected output files?","How do I match dynamic values in EK9 test output?"],"answer":"EK9 expected output files support type-aware placeholders that match dynamic values. Use {{Type}} syntax for values that change between test runs.\n\nPLACEHOLDER SYNTAX\nIn expected_output.txt, use {{Type}} placeholders:\n  Created at: {{DateTime}}\n  ID: {{GUID}}\n  Count: {{Integer}}\n\nAVAILABLE PLACEHOLDERS\n  {{String}}    matches any non-empty text\n  {{Integer}}   matches whole numbers\n  {{Float}}     matches decimal numbers\n  {{Boolean}}   matches true/false\n  {{Date}}      matches ISO dates\n  {{DateTime}}  matches ISO datetimes\n  {{GUID}}      matches UUID format\n  {{Money}}     matches currency format\n\nEXAMPLE EXPECTED OUTPUT FILE\nFor a program that prints a GUID and timestamp:\n  Record ID: {{GUID}}\n  Created: {{DateTime}}\n  Items: {{Integer}}\n  Active: {{Boolean}}\n\nSee Q204 for black-box testing. See Q205 for parameterized tests. See Q157 for running tests.","ek9Example":"defines module qa.testdeep.placeholders\n\n  defines program\n\n    DynamicOutputDemo()\n      stdout <- Stdout()\n\n      // === DYNAMIC VALUES IN OUTPUT ===\n      // When testing, some values change each run.\n      // EK9 expected_output.txt supports placeholders.\n\n      stdout.println(\"Dynamic output placeholders:\")\n      stdout.println(\"  {{String}}   - any non-empty text\")\n      stdout.println(\"  {{Integer}}  - whole numbers\")\n      stdout.println(\"  {{Float}}    - decimal numbers\")\n      stdout.println(\"  {{Boolean}}  - true or false\")\n      stdout.println(\"  {{Date}}     - ISO date format\")\n      stdout.println(\"  {{DateTime}} - ISO datetime format\")\n      stdout.println(\"  {{GUID}}     - UUID format\")\n      stdout.println(\"  {{Money}}    - currency format\")\n\n      // Example: a test printing dynamic values\n      // expected_output.txt would contain:\n      //   Record ID: {{GUID}}\n      //   Created: {{DateTime}}\n      //   Count: {{Integer}}\n\n      stdout.println(\"Placeholders match dynamic values in output\")","migrationContext":"Java: Hamcrest matchers or regex in assertions. Python: unittest with regex or mock. Rust: custom test helpers with regex. Go: regex assertions in test functions. Kotlin: custom matchers. EK9: type-aware placeholders in expected output files, validated at test runtime.","keywords":["anonymous","capture","closure","coverage","date","dynamic","expected","format","guid","match","output","placeholder","test","timestamp"],"primaryTopics":[],"typicalErrors":[{"error":"E50010","correct":"stdout <- Stdout()\n\n      // === DYNAMIC VALUES IN OUTPUT ===","incorrect":"stdout.println(\"Dynamic output placeholders:\")\n      stdout <- Stdout()","explanation":"Variables must be declared before use. Using stdout before declaring it is a forward reference. See ek9 -h E50010 for details."}],"companions":[]}
{"id":209,"category":"Testing","question":"How do I test that code throws or does not throw exceptions in EK9?","url":"https://ek9.io/qa/QA0209.html","alternatePhrasings":["How do I verify exceptions in EK9 tests?","How do assertThrows and assertDoesNotThrow work in EK9?","How do I test error handling in EK9?"],"answer":"EK9 provides try/catch for testing exception behavior, plus assertThrows and assertDoesNotThrow keywords for direct exception testing.\n\nTRY/CATCH FOR EXCEPTION TESTING\nUse try/catch to verify an exception is thrown:\n  try\n    riskyFunction()\n    assert false  // Should not reach here\n  catch\n    -> ex as Exception\n    assert ex?\n    stdout.println(\"Caught: \" + $ex)\n\nASSERTTHROWS\nVerify that a block throws an exception:\n  assertThrows\n    riskyFunction()\nThe test passes only if an exception is thrown.\n\nASSERTDOESNOTTHROW\nVerify that a block completes without throwing:\n  assertDoesNotThrow\n    safeFunction()\nThe test fails if any exception is thrown.\n\nTESTING EXCEPTION MESSAGES\nCatch and inspect exception details:\n  try\n    throwError()\n  catch\n    -> ex as Exception\n    assert $ex == \"expected message\"\n\nSee Q134 for try/catch basics. See Q136 for throwing exceptions. See Q156 for assertions.\n\nSee Q305 for require vs assert vs throw. See Q306 for assertThrows in depth. See Q307 for assertDoesNotThrow in depth.","ek9Example":"defines module qa.testdeep.exceptions\n\n  defines function\n\n    riskyDivide() as pure\n      ->\n        a as Integer\n        b as Integer\n      <-\n        rtn as Integer?\n\n      require b?\n      rtn: a / b\n\n  defines program\n\n    ExceptionTestingDemo()\n      stdout <- Stdout()\n\n      // === TRY/CATCH FOR EXCEPTION TESTING ===\n\n      try\n        result <- riskyDivide(10, 2)\n        stdout.println(`10 / 2 = ${result}`)\n      catch\n        -> ex as Exception\n        stdout.println(\"Unexpected error: \" + $ex)\n\n      // === TESTING ERROR CONDITIONS ===\n\n      try\n        discarded <- riskyDivide(10, 0)\n        stdout.println(\"Should not reach here \" + $discarded)\n      catch\n        -> ex as Exception\n        stdout.println(\"Caught expected error: \" + $ex)\n\n      // === ASSERTTHROWS / ASSERTDOESNOTTHROW ===\n      // In @Test programs:\n      //   assertThrows\n      //     riskyDivide(10, 0)\n      //   assertDoesNotThrow\n      //     riskyDivide(10, 2)\n\n      stdout.println(\"assertThrows verifies exception is thrown\")\n      stdout.println(\"assertDoesNotThrow verifies no exception\")","migrationContext":"Java: JUnit assertThrows(() -> code) and assertDoesNotThrow(). Python: pytest.raises(ExceptionType). Rust: #[should_panic] attribute. Go: custom helper checking error returns. Kotlin: assertThrows<Exception> { code }. EK9: assertThrows/assertDoesNotThrow keywords, plus try/catch for detailed inspection.","keywords":["assertDoesNotThrow","assertThrows","catch","coverage","error","exception","test","throw","verify"],"primaryTopics":[],"typicalErrors":[{"error":"E08091","correct":"    riskyDivide() as pure\n      ->\n        a as Integer\n        b as Integer\n      <-\n        rtn as Integer?\n\n      require b?\n      rtn: a / b","incorrect":"riskyDivide() as pure\n      ->\n        a as Integer\n        b as Integer\n      <-\n        rtn as Integer?\n\n      rtn: 0","explanation":"Both parameters a and b are declared but neither would be used in the function body if the implementation ignores them. All parameters must be referenced. See ek9 -h E08091 for details."}],"companions":[]}
{"id":210,"category":"Design Patterns and Idioms","question":"How does trait delegation with 'by' work in EK9?","url":"https://ek9.io/qa/QA0210.html","alternatePhrasings":["How do I delegate trait methods to a field in EK9?","What is the 'by' keyword for traits in EK9?","How do I use delegation instead of inheritance in EK9?"],"answer":"EK9 supports trait delegation with the 'by' keyword. A class can implement a trait and delegate unoverridden methods to a field.\n\nTRAIT DELEGATION SYNTAX\nDeclare a class that delegates trait methods:\n  FilteredLogger with trait of Logger by delegate\nHere 'delegate' is a field of type Logger. All Logger methods are forwarded to it unless overridden.\n\nSELECTIVE OVERRIDE\nOverride only the methods you want to customize:\n  override log()\n    -> msg as String\n    delegate.log(\"[FILTERED] \" + msg)\nThe logLevel() method is automatically delegated.\n\nWHY DELEGATION\nDelegation avoids tight coupling from inheritance. You can swap the delegate at runtime and only customize the methods that need different behavior.\n\nCOMPOSITION BENEFIT\nThe class wraps and decorates the delegate without extending it. This follows the principle of favoring composition over inheritance.\n\nSee Q106 for trait basics. See Q109 for composition. See Q115 for dynamic classes. See Q212 for composition over inheritance. See Q264 for adapter pattern using delegation. See Q266 for cross-cutting concerns via delegation.\n\nSee Q334 for transaction decorator pattern.","ek9Example":"defines module qa.patterns.delegation\n\n  defines trait\n\n    Logger\n      log() as abstract\n        -> msg as String\n\n      logLevel() as abstract\n        <- rtn as String?\n\n  defines class\n\n    ConsoleLogger with trait of Logger\n      override log()\n        -> msg as String\n        Stdout().println(\"[LOG] \" + msg)\n\n      override logLevel()\n        <- rtn as String: \"INFO\"\n\n      default operator ?\n\n    // Delegation: FilteredLogger delegates to ConsoleLogger\n    // Only overrides log(), logLevel() is auto-delegated\n    FilteredLogger with trait of Logger by delegate\n      delegate as Logger: ConsoleLogger()\n\n      FilteredLogger()\n        -> logger as Logger\n        this.delegate: logger\n\n      override log()\n        -> msg as String\n        delegate.log(\"[FILTERED] \" + msg)\n\n      default operator ?\n\n  defines program\n\n    TraitDelegationDemo()\n      stdout <- Stdout()\n\n      // === BASIC LOGGER ===\n\n      logger <- ConsoleLogger()\n      logger.log(\"Hello from console\")\n      stdout.println(`Level: ${logger.logLevel()}`)\n\n      // === DELEGATING LOGGER ===\n\n      filtered <- FilteredLogger(logger)\n      filtered.log(\"Hello through filter\")\n\n      // logLevel() is delegated to ConsoleLogger\n      stdout.println(`Filtered level: ${filtered.logLevel()}`)","migrationContext":"Java: no built-in delegation, manual forwarding methods. Kotlin: 'by' delegation on interfaces. Rust: no delegation, manual impl forwarding. Go: embedded structs provide forwarding. Python: __getattr__ for delegation. EK9: 'with trait of X by field' delegates all unoverridden methods to the field.","keywords":["abstract","by","compose","delegate","delegation","design","idiom","open","override","pattern","proxy","trait","virtual"],"primaryTopics":["delegation pattern","by keyword delegation"],"typicalErrors":[{"error":"E05120","correct":"override log()","incorrect":"log()","explanation":"When overriding a trait method in a delegating class, the 'override' keyword is required. FilteredLogger must use 'override log()' to customize the delegated Logger method. See ek9 -h E05120 for details."},{"error":"E05030","correct":"FilteredLogger with trait of Logger by delegate","incorrect":"FilteredLogger extends ConsoleLogger","explanation":"ConsoleLogger is closed by default and cannot be extended. EK9 types are closed unless declared 'as open'. Use trait delegation with 'by' instead of inheritance. See ek9 -h E05030 for details."}],"companions":[]}
{"id":211,"category":"Design Patterns and Idioms","question":"How do I implement double dispatch in EK9?","url":"https://ek9.io/qa/QA0211.html","alternatePhrasings":["How do I use dispatcher for the visitor pattern in EK9?","How do I replace the visitor pattern in EK9?","How does multi-method dispatch work in EK9?"],"answer":"EK9 has built-in double dispatch via the 'as dispatcher' modifier. Mark a method as a dispatcher and provide overloads for specific types. The runtime selects the most specific match.\n\nDISPATCHER METHOD\nMark the base method with 'as dispatcher':\n  render() as dispatcher\n    -> shape as Shape\nThis method is the dispatch entry point.\n\nSPECIFIC OVERLOADS\nProvide overloaded methods for specific subtypes:\n  render()\n    -> circle as Circle\n    stdout.println(\"Rendering circle\")\n  render()\n    -> rect as Rectangle\n    stdout.println(\"Rendering rectangle\")\n\nRUNTIME DISPATCH\nCalling render(someShape) dispatches to the most specific overload based on the actual runtime type of someShape.\n\nNO VISITOR BOILERPLATE\nUnlike the Visitor pattern, no accept() methods needed on element classes. The dispatcher handles type resolution automatically.\n\nSee Q60 for function dispatching. See Q103 for abstract classes. See Q105 for method dispatch. See Q254 for the Any type as dispatcher fallback. See Q255 for method resolution costs in dispatching.","ek9Example":"defines module qa.patterns.doubledispatch\n\n  defines class\n\n    Shape as abstract\n      shapeName as String?\n\n      default private Shape() as pure\n\n      Shape() as pure\n        -> n as String\n        shapeName :=? String(n)\n\n      name() as pure\n        <- rtn as String: String(shapeName)\n\n      override operator ? as pure\n        <- rtn as Boolean: shapeName?\n\n    Circle is Shape\n\n      Circle() as pure\n        super(\"Circle\")\n\n    Rectangle is Shape\n\n      Rectangle() as pure\n        super(\"Rectangle\")\n\n    // Renderer with dispatcher-based double dispatch\n    Renderer\n      stdout as Stdout: Stdout()\n\n      // Base dispatcher method\n      render() as dispatcher\n        -> shape as Shape\n        stdout.println(\"Rendering generic shape: \" + shape.name())\n\n      // Specific overload for Circle\n      render()\n        -> circle as Circle\n        stdout.println(\"Rendering circle: \" + circle.name())\n\n      // Specific overload for Rectangle\n      render()\n        -> rect as Rectangle\n        stdout.println(\"Rendering rectangle: \" + rect.name())\n\n      default operator ?\n\n  defines program\n\n    DoubleDispatchDemo()\n\n      renderer <- Renderer()\n\n      // === DIRECT CALLS ===\n\n      renderer.render(Circle())\n      renderer.render(Rectangle())\n\n      // === DISPATCH VIA BASE TYPE ===\n      // When called with Shape reference,\n      // dispatcher resolves to most specific overload\n\n      shapes <- List() of Shape\n      shapes += Circle()\n      shapes += Rectangle()\n\n      for shape in shapes\n        renderer.render(shape)","migrationContext":"Java: Visitor pattern with accept/visit methods, or instanceof chains. Kotlin: when + is checks with sealed classes. C++: double dispatch via RTTI or visitor. Rust: enum match (no double dispatch). Go: type switch. EK9: 'as dispatcher' modifier with overloaded methods, runtime selects most specific match.","keywords":["design","dispatch","dispatcher","double","handler","idiom","multiple","overload","pattern","runtime","sealed","type","visitor"],"primaryTopics":[],"typicalErrors":[{"error":"E50060","correct":"stdout.println(\"Rendering generic shape: \" + shape.name())","incorrect":"stdout.println(\"Rendering generic shape: \" + shape.label())","explanation":"Shape does not have a label() method. The correct method name is name(). Calling a non-existent method triggers E50060 — method not resolved. See ek9 -h E50060 for details."}],"companions":[],"oracleToolHint":{"tool":"ek9_scaffold","intent":"dispatcher","description":"Oracle can generate a dispatcher with handler methods for a type hierarchy."}}
{"id":212,"category":"Design Patterns and Idioms","question":"How do I apply the composition-over-inheritance pattern in EK9?","url":"https://ek9.io/qa/QA0212.html","alternatePhrasings":["Why does EK9 prefer composition over inheritance?","How do I wrap a collection in EK9?","How do I encapsulate a List in EK9?"],"answer":"EK9 types are closed by default, encouraging composition over inheritance. Wrap a collection or type as a private field and expose only the operations you need.\n\nCOMPOSITION PATTERN\nWrap a collection as a private field:\n  TaskQueue\n    items as List of String\nExpose only the methods that make sense:\n  addTask(), pendingCount(), hasWork()\n\nMULTI-CONCERN COMPOSITION\nComposition shines when wrapping multiple concerns. ValidatedConfig wraps a Dict and adds validation:\n  ValidatedConfig\n    entries as Dict of (String, String)\n    get() with default, set() with validation, has() for existence\nThe Dict is hidden behind a controlled API that enforces business rules.\n\nWHY COMPOSITION\nBuilt-in types like List and Dict are closed (cannot be extended). This prevents mixing collection behavior with application logic. Composition gives you control over the public API.\n\nWHEN INHERITANCE IS APPROPRIATE\nInheritance is still correct for genuine IS-A relationships. Use 'as open' on the base class and extend when the subtype truly specializes the base.\n\nCONTROLLED INTERFACE\nWith composition, clients see only:\n  config.get(key), config.set(key, value), config.has(key)\nNot the full Dict API (merge, replace, iterate, etc.).\n\nSee Q101 for closed by default. See Q128 for closed collections. See Q109 for composition. See Q210 for trait delegation. See Q264 for adapter pattern using composition. See Q266 for cross-cutting concerns via delegation. See Q278 for why AI extends closed types. See Q315 for inheritance depth limits that encourage composition.","ek9Example":"defines module qa.patterns.composition\n\n  defines class\n\n    TaskQueue\n      items as List of String: List() of String\n\n      default TaskQueue()\n\n      addTask()\n        -> task as String\n        items += task\n\n      pendingCount() as pure\n        <- rtn as Integer: length items\n\n      hasWork() as pure\n        <- rtn as Boolean: length items > 0\n\n      default operator ?\n\n    // Multi-concern composition: Dict + validation\n    ValidatedConfig\n      entries as Dict of (String, String): Dict() of (String, String)\n\n      default ValidatedConfig()\n\n      get()\n        -> key as String\n        <- rtn as String: entries.getOrDefault(key, \"\")\n\n      set()\n        ->\n          key as String\n          setting as String\n        <- rtn as Boolean: false\n        if key? and setting?\n          entries += DictEntry(key, setting)\n          rtn: true\n\n      has() as pure\n        -> key as String\n        <- rtn as Boolean: entries contains key\n\n      entryCount() as pure\n        <- rtn as Integer: length entries\n\n      default operator ?\n\n  defines program\n\n    CompositionDemo()\n      stdout <- Stdout()\n\n      // === TASK QUEUE: LIST COMPOSITION ===\n\n      queue <- TaskQueue()\n      queue.addTask(\"Build feature\")\n      queue.addTask(\"Write tests\")\n      queue.addTask(\"Review code\")\n\n      stdout.println(`Pending tasks: ${queue.pendingCount()}`)\n      stdout.println(`Has work: ${queue.hasWork()}`)\n\n      // === VALIDATED CONFIG: DICT COMPOSITION ===\n\n      config <- ValidatedConfig()\n      hostName <- \"host\"\n      config.set(hostName, \"localhost\")\n      config.set(\"port\", \"8080\")\n\n      stdout.println(`Has host: ${config.has(hostName)}`)\n      stdout.println(`Host: ${config.get(hostName)}`)\n      stdout.println(`Entries: ${config.entryCount()}`)\n\n      // Clients use get/set/has, not the full Dict API\n      stdout.println(\"Composition controls the public API\")","migrationContext":"Java: ArrayList<T> is open, commonly extended (bad practice). Python: list is open. Rust: no inheritance, composition by default. Go: embedding for delegation. Kotlin: classes final by default like EK9. EK9: types closed by default, composition is the natural and preferred pattern.","keywords":["closed","composition","delegate","design","encapsulate","idiom","inheritance","migrate","pattern","wrap"],"primaryTopics":["composition over inheritance","composition pattern"],"typicalErrors":[{"error":"E50060","correct":"rtn as String: entries.getOrDefault(key, \"\")","incorrect":"rtn as String: entries.get(key)","explanation":"Dict does not have a get() method. Use getOrDefault() with a fallback value. Calling a non-existent method triggers E50060 — method not resolved. See ek9 -h E50060 for details."}],"companions":[]}
{"id":213,"category":"Design Patterns and Idioms","question":"How do I protect shared data with MutexLock in EK9?","url":"https://ek9.io/qa/QA0213.html","alternatePhrasings":["How do I use MutexLock with a callback in EK9?","What is the MutexKey pattern in EK9?","How do I safely update locked data in EK9?"],"answer":"MutexLock of T wraps a value and controls access through MutexKey callback functions. The canonical EK9 pattern is to hold the lock as a FIELD on a wrapping class and expose the protected operations as class methods. The lock never escapes the wrapping class.\n\nFIELD-ON-CLASS PATTERN\nDeclare the MutexLock as a field with an inline initialiser:\n  Inventory\n    lockedStock as MutexLock of List of String: MutexLock(List() of String)\n\nThe field initialiser is the only legal MutexLock construction site (E08254/E08256 reject locals and collections of locks).\n\nMUTEXKEY CALLBACK\nInside each method on the wrapping class, declare a dynamic MutexKey function. The function body is the critical section; lockedItem is the protected value:\n  add()\n    -> item as String\n    accessKey <- (item) is MutexKey of List of String as function\n      lockedItem += item\n    require lockedStock.enter(accessKey)\n\nENTER THE LOCK\nCall enter() (blocking) or tryEnter() (non-blocking) inside the class's own method. The lock releases automatically when the callback returns.\n\nCOPY DATA OUT\nTo expose a snapshot of the protected value, copy it out with :=: into a return-target captured in the callback:\n  read()\n    <- rtn as List of String: List() of String\n    accessKey <- (rtn) is MutexKey of List of String as function\n      rtn :=: lockedItem\n    require lockedStock.enter(accessKey)\n\nWHY FIELD-ON-CLASS\nEK9's compile-time data-race detection needs every lock to have a stable field-rooted home. Locks declared as locals or held inside collections defeat the static analysis. Wrapping the lock in a class also keeps the API surface small — callers see only the protected operations.\n\nSee Q158 for MutexLock basics. See Q159 for parallel processing. See Q52 for dynamic functions. See Q267 for anti-patterns including mutable shared state.","ek9Example":"defines module qa.patterns.mutex\n\n  defines class\n\n    //Canonical pattern: MutexLock as a field on a wrapping class.\n    //The class methods are the only API to the protected stock list;\n    //the lock itself never escapes.\n    Inventory\n      lockedStock as MutexLock of List of String: MutexLock(List() of String)\n\n      add()\n        -> item as String\n\n        accessKey <- (item) is MutexKey of List of String as function\n          lockedItem += item\n\n        require lockedStock.enter(accessKey)\n\n      read()\n        <- rtn as List of String: List() of String\n\n        accessKey <- (rtn) is MutexKey of List of String as function\n          rtn :=: lockedItem\n\n        require lockedStock.enter(accessKey)\n\n      default operator ?\n\n  defines program\n\n    MutexPatternDemo()\n      stdout <- Stdout()\n\n      //Construct the wrapper — the MutexLock is created by the\n      //class's field initialiser, not here in user code.\n      inventory <- Inventory()\n\n      inventory.add(\"widgets\")\n      inventory.add(\"sprockets\")\n\n      snapshot <- inventory.read()\n      stdout.println(`Inventory snapshot: ${$snapshot}`)","migrationContext":"Java: synchronized blocks or ReentrantLock with try/finally. Python: threading.Lock with context manager. Rust: Mutex<T>::lock() returns MutexGuard. Go: sync.Mutex with Lock()/Unlock(). EK9: MutexLock as a class field, accessed through MutexKey callbacks inside class methods. The field-rooted home enables compile-time data-race and deadlock detection.","keywords":["class","concurrent","design","enter","field","idiom","key","lock","mutex","pattern","protect","safety","shared","thread"],"primaryTopics":[],"typicalErrors":[{"error":"E08256","correct":"Inventory\n      lockedStock as MutexLock of List of String: MutexLock(List() of String)","incorrect":"lock <- MutexLock(List() of String)   //local — rejected","explanation":"MutexLock must be declared as a field on a class with an inline initialiser. The field-rooted home is required for compile-time lock-identity tracking. See ek9 -h E08256 for details."}],"companions":[]}
{"id":214,"category":"Design Patterns and Idioms","question":"How do I implement the strategy pattern in EK9?","url":"https://ek9.io/qa/QA0214.html","alternatePhrasings":["How do I swap algorithms at runtime in EK9?","How do I use functions as strategies in EK9?","How do I inject behavior via function parameters in EK9?"],"answer":"EK9 implements the strategy pattern using abstract functions and dynamic function implementations. Pass different function instances to swap behavior.\n\nABSTRACT FUNCTION AS STRATEGY\nDefine the strategy interface as an abstract function:\n  Formatter as abstract function\n    -> item as String\n    <- rtn as String?\n\nDYNAMIC IMPLEMENTATIONS\nCreate concrete strategies with dynamic functions:\n  upperFormatter <- () is Formatter as function\n    rtn: item.upperCase()\n  lowerFormatter <- () is Formatter as function\n    rtn: item.lowerCase()\n\nPROCESSOR WITH STRATEGY\nPass the strategy as a parameter:\n  Processor\n    process()\n      ->\n        formatter as Formatter\n        text as String\n      <- rtn as String: formatter(text)\n\nSWAPPING STRATEGIES\nUse different formatters without changing Processor:\n  processor.process(upperFormatter, \"hello\")\n  processor.process(lowerFormatter, \"HELLO\")\n\nSee Q57 for strategy without subclassing. See Q52 for dynamic functions. See Q55 for function delegation. See Q210 for trait delegation as an alternative to function strategies.","ek9Example":"defines module qa.patterns.strategy\n\n  defines function\n\n    Formatter() as abstract\n      -> item as String\n      <- rtn as String?\n\n  defines class\n\n    TextProcessor\n      process()\n        ->\n          formatter as Formatter\n          text as String\n        <- rtn as String: formatter(text)\n\n      default operator ?\n\n  defines program\n\n    StrategyPatternDemo()\n      stdout <- Stdout()\n\n      // === DYNAMIC FUNCTION STRATEGIES ===\n\n      upperFormatter <- () is Formatter as function\n        rtn: item.upperCase()\n\n      lowerFormatter <- () is Formatter as function\n        rtn: item.lowerCase()\n\n      // === PROCESSOR USES STRATEGY ===\n\n      textProcessor <- TextProcessor()\n\n      upper <- textProcessor.process(upperFormatter, \"hello world\")\n      stdout.println(`Upper: ${upper}`)\n\n      lower <- textProcessor.process(lowerFormatter, \"HELLO WORLD\")\n      stdout.println(`Lower: ${lower}`)\n\n      // === SWAP STRATEGIES FREELY ===\n\n      stdout.println(\"Strategy pattern: swap behavior via function parameters\")","migrationContext":"Java: Strategy interface with implementing classes. Python: pass functions directly (first-class). Rust: trait objects or closures. Go: function types. Kotlin: function types or interface implementations. EK9: abstract function as strategy interface, dynamic functions as implementations.","keywords":["algorithm","behavior","delegate","design","flexible","function","idiom","inject","pattern","strategy","swap"],"primaryTopics":["strategy pattern","strategy"],"typicalErrors":[{"error":"E07110","correct":"Formatter() as abstract","incorrect":"Formatter() as open","explanation":"Abstract functions define a strategy interface with no implementation. Using 'as open' instead of 'as abstract' triggers E07110 because 'open' is for functions with a body that can be overridden, not for bodyless declarations. See ek9 -h E07110 for details."}],"companions":[]}
{"id":215,"category":"Security and Sanitization","question":"How does the 'sanitized' parameter modifier work in EK9?","url":"https://ek9.io/qa/QA0215.html","alternatePhrasings":["How do I mark parameters as sanitized in EK9?","How does EK9 track tainted input?","How do I handle user input safely in EK9?"],"answer":"The 'sanitized' modifier on parameters tells the compiler to track input that needs careful handling. It enables compile-time input validation tracking.\n\nSANITIZED PARAMETER\nMark a parameter as sanitized:\n  processInput() as pure\n    -> input as sanitized String\nThe compiler knows this parameter contains external, potentially dangerous data.\n\nCOPY CONSTRUCTOR PATTERN\nIn pure contexts, create a safe local copy:\n  localCopy <- String(input)\nThis breaks any reference aliasing and creates independent data.\n\nNON-PURE CONTEXT\nIn non-pure functions, use :=: or :~: on initialized variables:\n  safeCopy <- String(sql)\n  result: \"\"\n  result :=: safeCopy\n\nDIRECT USE IN EXPRESSIONS\nUsing sanitized parameters in expressions is safe because it creates new values:\n  output: \"Processed: \" + input\n\nSee Q216 for threat detection. See Q217 for pure function interaction. See Q49 for function basics. See Q54 for pure functions. See Q269 for input validation. See Q271 for secrets and environment variables. See Q272 for defense in depth.\n\nSee Q661 for sanitized parameter restrictions. See Q662 for safe copy patterns. See Q663 for override matching.","ek9Example":"defines module qa.security.sanitized\n\n  defines function\n\n    // Pure function with sanitized parameter\n    pureProcess() as pure\n      -> input as sanitized String\n      <- result as String?\n\n      // Copy constructor creates safe local copy\n      localCopy <- String(input)\n      result: \"Processed: \" + localCopy\n\n    // Non-pure function with sanitized parameter\n    queryProcess()\n      -> sql as sanitized String\n      <- result as String?\n\n      // Copy constructor first\n      safeCopy <- String(sql)\n      result: \"\"\n      result :=: safeCopy\n\n    // Direct expression use is safe\n    logInput() as pure\n      -> userInput as sanitized String\n      <- output as String?\n\n      // Concatenation creates new string\n      output: \"Log: \" + userInput\n\n  defines program\n\n    SanitizedParameterDemo()\n      stdout <- Stdout()\n\n      // === PURE FUNCTION WITH SANITIZED ===\n\n      query <- \"SELECT * FROM users\"\n      result1 <- pureProcess(query)\n      if result1?\n        stdout.println(result1)\n\n      // === NON-PURE WITH COPY ===\n\n      result2 <- queryProcess(query)\n      if result2?\n        stdout.println(\"Query: \" + result2)\n\n      // === DIRECT EXPRESSION ===\n\n      result3 <- logInput(\"user data\")\n      if result3?\n        stdout.println(result3)","migrationContext":"Java: no language-level sanitization, relies on OWASP libraries. Python: no built-in taint tracking. Rust: newtype pattern for manual tracking. Go: no taint tracking. Kotlin: no sanitization modifier. EK9: 'sanitized' keyword on parameters enables compile-time tracking of tainted data.","keywords":["compile-time","copy","injection","input","protect","safe","sanitized","security","tainted","validation"],"primaryTopics":["sanitized","input sanitization","XSS prevention"],"typicalErrors":[{"error":"E50060","correct":"localCopy <- String(input)","incorrect":"localCopy <- input.clone()","explanation":"String does not have a clone() method. Use the copy constructor String(value) to create a defensive copy. Calling a non-existent method triggers E50060 — method not resolved. See ek9 -h E50060 for details."}],"companions":[]}
{"id":216,"category":"Security and Sanitization","question":"How does EK9 detect and report security threats?","url":"https://ek9.io/qa/QA0216.html","alternatePhrasings":["What security threats does EK9 detect at compile time?","How does EK9 prevent injection attacks?","What security analysis does the EK9 compiler perform?"],"answer":"EK9 detects common security threats at compile time through sanitized parameter tracking and static analysis.\n\nSQL INJECTION DETECTION\nWhen a sanitized String parameter is used in database contexts, the compiler tracks that untrusted data flows through the system, ensuring copy constructor patterns are used.\n\nXSS DETECTION\nSanitized parameters used in output contexts are tracked. The copy constructor pattern ensures tainted HTML/JavaScript cannot pass through unchecked.\n\nCOMMAND INJECTION DETECTION\nSanitized parameters in command execution contexts are flagged. The compiler ensures defensive copies prevent shell injection.\n\nCOMPILE-TIME ENFORCEMENT\nThe compiler enforces the sanitization pattern:\n  1. Mark external input as sanitized\n  2. Create defensive copies via constructors\n  3. Use copies for processing\nViolating this pattern produces compile errors.\n\nSee Q215 for sanitized parameters. See Q134 for exception handling. See Q218 for security best practices. See Q268 for OWASP vulnerability prevention. See Q272 for defense in depth.","ek9Example":"defines module qa.security.threats\n\n  defines function\n\n    // Sanitized parameter with copy constructor pattern\n    processUserQuery() as pure\n      -> userInput as sanitized String\n      <- result as String?\n\n      // Defensive copy — prevents injection\n      safeCopy <- String(userInput)\n      result: \"Safe query: \" + safeCopy\n\n    // Passing sanitized to another function\n    handleRequest() as pure\n      -> requestData as sanitized String\n      <- result as String?\n\n      // Pass to helper for processing\n      result: processUserQuery(requestData)\n\n  defines program\n\n    ThreatDetectionDemo()\n      stdout <- Stdout()\n\n      // === COMPILE-TIME SECURITY ===\n\n      rawInput <- \"user-provided data\"\n      safe <- processUserQuery(rawInput)\n      if safe?\n        stdout.println(safe)\n\n      // === THREAT CATEGORIES ===\n\n      stdout.println(\"EK9 detects at compile time:\")\n      stdout.println(\"  SQL injection via sanitized tracking\")\n      stdout.println(\"  XSS via output context analysis\")\n      stdout.println(\"  Command injection via parameter flow\")\n      stdout.println(\"Copy constructor pattern prevents all three\")","migrationContext":"Java: OWASP ESAPI or FindBugs for taint analysis (external tools). Python: Bandit for security linting. Rust: no taint tracking, but ownership prevents some classes. Go: no built-in, gosec for analysis. EK9: compile-time sanitization tracking detects SQL, XSS, and command injection threats.","keywords":["command","detection","injection","migrate","protect","report","safe","security","sql","threat","xss"],"primaryTopics":[],"typicalErrors":[{"error":"E50060","correct":"safeCopy <- String(userInput)","incorrect":"safeCopy <- userInput.clone()","explanation":"String does not have a clone() method. Use the copy constructor String(value) to create a defensive copy. Calling a non-existent method triggers E50060 — method not resolved. See ek9 -h E50060 for details."}],"companions":[]}
{"id":217,"category":"Security and Sanitization","question":"How do sanitized parameters interact with pure functions in EK9?","url":"https://ek9.io/qa/QA0217.html","alternatePhrasings":["Why must pure functions use copy constructors with sanitized parameters?","How do purity and sanitization work together in EK9?","What is the copy constructor pattern for sanitized parameters?"],"answer":"In pure functions, sanitized parameters must use the copy constructor pattern because reassignment is forbidden. Purity and security reinforce each other.\n\nPURE + SANITIZED = COPY CONSTRUCTOR\nPure functions cannot reassign, so the only option is:\n  pureProcess() as pure\n    -> input as sanitized String\n    <- result as String?\n    localCopy <- String(input)\n    result: localCopy\nThe copy constructor creates an independent value.\n\nWHY THEY INTERACT\nPurity prevents mutation, sanitization tracks tainted data. Together they force the safest pattern: create a defensive copy and use only the copy. No escape hatches.\n\nNON-PURE ALTERNATIVES\nNon-pure functions have additional options:\n  safeCopy <- String(input)\n  result: \"\"\n  result :=: safeCopy     // copy operator\n  result :~: safeCopy     // merge operator\n\nEXPRESSION USE\nBoth pure and non-pure can use sanitized parameters directly in expressions:\n  result: \"Prefix: \" + input\nConcatenation creates a new value, so no aliasing risk.\n\nSee Q215 for sanitized basics. See Q54 for pure functions. See Q141 for constant immutability. See Q273 for purity as security boundary.\n\nSee Q662 for safe copy patterns. See Q666 for injection pure context.","ek9Example":"defines module qa.security.pure\n\n  defines function\n\n    // Pure function MUST use copy constructor\n    pureWithSanitized() as pure\n      -> input as sanitized String\n      <- result as String?\n\n      // Only option in pure context: copy constructor\n      cleanInput <- String(input)\n      result: \"Clean: \" + cleanInput\n\n    // Non-pure has more options\n    nonPureWithSanitized()\n      -> input as sanitized String\n      <- result as String?\n\n      // Option 1: Copy constructor (works everywhere)\n      safeCopy <- String(input)\n\n      // Option 2: :=: on initialized variable\n      result: \"\"\n      result :=: safeCopy\n\n    // Direct expression use works in both contexts\n    directUse() as pure\n      -> input as sanitized String\n      <- result as String?\n\n      // Concatenation creates new value — safe\n      result: \"Output: \" + input\n\n  defines program\n\n    SanitizedPureDemo()\n      stdout <- Stdout()\n\n      userInput <- \"external input\"\n\n      // === PURE + SANITIZED ===\n\n      result1 <- pureWithSanitized(userInput)\n      if result1?\n        stdout.println(result1)\n\n      // === NON-PURE + SANITIZED ===\n\n      result2 <- nonPureWithSanitized(userInput)\n      if result2?\n        stdout.println(result2)\n\n      // === DIRECT EXPRESSION ===\n\n      result3 <- directUse(userInput)\n      if result3?\n        stdout.println(result3)\n\n      stdout.println(\"Purity + sanitization = forced defensive copies\")","migrationContext":"Java: no purity concept, PreparedStatement for SQL safety. Python: no purity enforcement. Rust: ownership model prevents some aliasing. Go: no purity. Kotlin: no purity. Haskell: purity prevents side effects but no sanitization concept. EK9: purity and sanitization synergize, forcing defensive copy pattern.","keywords":["constructor","context","copy","defensive","function","immutable","protect","pure","purity","safe","sanitize","sanitized","security","side-effect"],"primaryTopics":[],"typicalErrors":[{"error":"E50060","correct":"cleanInput <- String(input)","incorrect":"cleanInput <- input.copy()","explanation":"String does not have a copy() method. Use the copy constructor String(value) to create a defensive copy. Calling a non-existent method triggers E50060 — method not resolved. See ek9 -h E50060 for details."}],"companions":[]}
{"id":218,"category":"Security and Sanitization","question":"What are the security best practices in EK9?","url":"https://ek9.io/qa/QA0218.html","alternatePhrasings":["How do I write secure code in EK9?","What security features does EK9 provide?","How do I prevent security vulnerabilities in EK9?"],"answer":"EK9 provides multiple security features that work together. Following these best practices prevents common vulnerability classes.\n\nSANITIZED PARAMETERS\nMark external input with 'sanitized':\n  -> userInput as sanitized String\nThe compiler tracks tainted data flow.\n\nCOPY CONSTRUCTOR PATTERN\nAlways create defensive copies:\n  localCopy <- String(userInput)\nPrevents reference aliasing and injection.\n\nPURE FUNCTIONS\nUse pure functions for data processing:\n  process() as pure\nPurity prevents side effects and forces safe data handling.\n\nREQUIRE VALIDATION\nUse require for input validation:\n  require input?\n  require length input > 0\nFails fast on invalid input.\n\nMUTEXLOCK FOR SHARED STATE\nProtect shared data with a MutexLock held as a FIELD on a wrapping class (locals are rejected by E08256):\n  SharedCache\n    locked as MutexLock of Dict of (String, String): MutexLock(Dict() of (String, String))\nThe wrapping class's methods are the only API to the protected data; MutexKey callbacks define the critical sections. The field-rooted home enables compile-time data-race detection. See Q158.\n\nTYPE SAFETY\nEK9's strong typing prevents type confusion attacks. No implicit conversions, no null references.\n\nSee Q215 for sanitized parameters. See Q29 for unset variables. See Q158 for MutexLock.\n\nSee Q215 for sanitized parameters. See Q217 for sanitized and pure. See Q268 for OWASP vulnerability prevention. See Q272 for defense in depth.","ek9Example":"defines module qa.security.bestpractices\n\n  defines function\n\n    // Best practice: sanitized + pure + copy constructor\n    secureProcess() as pure\n      -> input as sanitized String\n      <- result as String?\n\n      // 1. Copy constructor for defensive copy\n      cleanInput <- String(input)\n\n      // 2. Validate with require\n      require cleanInput?\n\n      // 3. Process the clean copy\n      result: \"Secure: \" + cleanInput\n\n  defines program\n\n    SecurityBestPracticesDemo()\n      stdout <- Stdout()\n\n      userInput <- \"user input\"\n\n      // === SANITIZED + PURE + REQUIRE ===\n\n      result <- secureProcess(userInput)\n      if result?\n        stdout.println(result)\n\n      // === SECURITY FEATURES SUMMARY ===\n\n      stdout.println(\"EK9 security best practices:\")\n      stdout.println(\"  1. sanitized on external input\")\n      stdout.println(\"  2. Copy constructor for defensive copies\")\n      stdout.println(\"  3. Pure functions for processing\")\n      stdout.println(\"  4. require for input validation\")\n      stdout.println(\"  5. MutexLock for shared state\")\n      stdout.println(\"  6. Strong typing prevents confusion\")","migrationContext":"Java: OWASP guidelines, Spring Security, manual defensive coding. Python: input validation libraries, no compile-time safety. Rust: ownership model prevents many bugs. Go: manual security practices. Kotlin: null safety helps but no sanitization. EK9: sanitized keyword, pure functions, require validation, MutexLock, and strong typing provide layered security.","keywords":["best","immutable","mutex","practices","protect","pure","require","safe","safety","sanitized","security","side-effect","type"],"primaryTopics":[],"typicalErrors":[{"error":"E50060","correct":"cleanInput <- String(input)","incorrect":"cleanInput <- input.sanitize()","explanation":"String does not have a sanitize() method. Use the copy constructor String(value) to create a defensive copy. The sanitized modifier on the parameter is the security mechanism, not a method call. See ek9 -h E50060 for details."}],"companions":[]}
{"id":219,"category":"Enumerations","question":"What do I get for free with an EK9 enumeration?","url":"https://ek9.io/qa/QA0219.html","alternatePhrasings":["What operators does an EK9 enum automatically have?","How many members does an EK9 enumeration generate?","What comes built-in with EK9 enumerations?"],"answer":"From a bare list of values you get 28 auto-generated members: 24 operators, 3 constructors, and an iterator. No annotations, no derive macros, no boilerplate.\n\nCOMPARISON OPERATORS (7 enum-vs-enum)\nEvery enumeration gets ==, <>, <, >, <=, >=, and <=> for full ordering based on declaration order. No Comparable interface to implement.\n\nSTRING COMPARISON OPERATORS (7 enum-vs-String)\nThe same seven comparison operators work with String arguments directly: season == \"Spring\" works without any conversion.\n\nCONVERSION OPERATORS\n$ converts to String, #^ promotes to String, $$ converts to JSON, and #? returns a hashcode Integer.\n\nQUERY OPERATORS\n? checks if the enum is set. #< returns the first declared member. #> returns the last declared member.\n\nMUTATING OPERATORS\n++ advances to the next member. -- goes to the previous member. Both go to unset at boundaries.\n\nCONSTRUCTORS (3)\nColour() creates unset. Colour(\"Red\") creates from string (unset if invalid). Colour(existing) copies.\n\nITERATOR\n'for colour in Colour' iterates all members. 'cat Colour' streams them.\n\nSee Q99 for basic enumeration syntax. See Q220 for navigation with increment and decrement. See Q221 for string comparison details. See Q96 for class operators.","ek9Example":"defines module qa.enums.batteries\n\n  defines type\n\n    Colour\n      Red\n      Green\n      Blue\n\n  defines program\n\n    EnumBatteriesDemo()\n      stdout <- Stdout()\n\n      red <- Colour.Red\n      blue <- Colour.Blue\n\n      // === COMPARISON OPERATORS (enum vs enum) ===\n\n      stdout.println(`Equal: ${red == Colour.Red}`)\n      stdout.println(`Not equal: ${red <> blue}`)\n      stdout.println(`Less than: ${red < blue}`)\n      stdout.println(`Greater than: ${blue > red}`)\n      stdout.println(`Less or equal: ${red <= Colour.Red}`)\n      stdout.println(`Greater or equal: ${blue >= red}`)\n      stdout.println(`Compare: ${red <=> blue}`)\n\n      // === STRING COMPARISON OPERATORS ===\n\n      stdout.println(`Eq string: ${red == \"Red\"}`)\n      stdout.println(`Neq string: ${red <> \"Blue\"}`)\n      stdout.println(`Lt string: ${red < \"Green\"}`)\n\n      // === CONVERSION OPERATORS ===\n\n      stdout.println(`String: ${red}`)\n      stdout.println(`Promote: ${#^red}`)\n      stdout.println(`JSON: ${$red}`)\n      stdout.println(`Hashcode: ${#?red}`)\n\n      // === QUERY OPERATORS ===\n\n      stdout.println(`IsSet: ${red?}`)\n      stdout.println(`First: ${#< red}`)\n      stdout.println(`Last: ${#> red}`)\n\n      // === CONSTRUCTORS ===\n\n      fromString <- Colour(\"Green\")\n      stdout.println(`From string: ${fromString}`)\n\n      invalid <- Colour(\"Purple\")\n      stdout.println(`Invalid isSet: ${invalid?}`)\n\n      unsetColour <- Colour()\n      stdout.println(`Unset isSet: ${unsetColour?}`)\n\n      copied <- Colour(red)\n      stdout.println(`Copied: ${copied}`)\n\n      // === ITERATION ===\n\n      for colour in Colour\n        stdout.println(`Iterate: ${colour}`)\n\n      colours <- cat Colour | collect as List of Colour\n      stdout.println(`Collected: ${length colours}`)","migrationContext":"Java: toString(), valueOf(), compareTo(), equals() generated but no first/last, no increment/decrement, no string-parameter comparison, no JSON, no safe string construction. Python: needs @total_ordering for ordering, no iteration operators, no JSON. Rust: needs #[derive(PartialOrd, Ord, PartialEq, Eq)] plus strum crate for iteration and string conversion. Go: hand-written everything, iota gives integers only. Kotlin: entries property but no ordering operators, no increment/decrement, no JSON. Swift: CaseIterable for iteration, Comparable conformance needed for ordering, no increment/decrement, no string construction from String, associated values add power but also complexity. C#: limited to integer-based comparison, no string construction. EK9: 28 members from a bare value list, zero boilerplate.","keywords":["automatic","batteries","boilerplate","built-in","enumeration","free","generated","included","migrate","operator","swift"],"primaryTopics":[],"typicalErrors":[{"error":"E50060","correct":"      stdout.println(`String: ${red}`)","incorrect":"      stdout.println(`String: ${red.toString()}`)","explanation":"EK9 enumerations have no toString() method — use the $ operator (or plain ${...} interpolation) for String conversion. See ek9 -h E50060 for details."}],"companions":[]}
{"id":220,"category":"Enumerations","question":"How do I navigate between enum values in EK9?","url":"https://ek9.io/qa/QA0220.html","alternatePhrasings":["How do increment and decrement work on EK9 enums?","What happens when you go past the last enum value?","How do I get the first or last enum value in EK9?"],"answer":"EK9 enumerations support ++ (increment), -- (decrement), #< (first), and #> (last) operators for navigating between values.\n\nINCREMENT AND DECREMENT\nThe ++ operator moves to the next declared value. The -- operator moves to the previous. Both are mutating operators that change the variable in place.\n\nBOUNDARY TO UNSET\nIncrementing past the last value makes the enum unset. Decrementing before the first value also makes it unset. No ArrayIndexOutOfBounds. No wrap-around confusion. The result is safe, predictable, and testable with the ? operator.\n\nFIRST AND LAST\nThe #< operator always returns the first declared value. The #> operator always returns the last. These work on any set enum variable and return a set result regardless of which value the variable holds.\n\nSAFE CYCLING\nTo safely cycle through enum values, check with ? after each increment. When the result becomes unset, you have exhausted all values.\n\nSee Q219 for all auto-generated operators. See Q99 for basic enumeration. See Q222 for unset semantics.","ek9Example":"defines module qa.enums.navigation\n\n  defines type\n\n    TrafficLight\n      Red\n      Amber\n      Green\n\n  defines program\n\n    EnumNavigationDemo()\n      stdout <- Stdout()\n\n      // === INCREMENT THROUGH VALUES ===\n\n      light <- TrafficLight.Red\n      stdout.println(`Start: ${light}`)\n\n      light++\n      stdout.println(`After ++: ${light}`)\n\n      light++\n      stdout.println(`After ++ again: ${light}`)\n\n      // === BOUNDARY: increment past last goes unset ===\n\n      light++\n      stdout.println(`Past last isSet: ${light?}`)\n\n      // === DECREMENT THROUGH VALUES ===\n\n      current <- TrafficLight.Green\n      stdout.println(`Start: ${current}`)\n\n      current--\n      stdout.println(`After --: ${current}`)\n\n      current--\n      stdout.println(`After -- again: ${current}`)\n\n      // === BOUNDARY: decrement before first goes unset ===\n\n      current--\n      stdout.println(`Before first isSet: ${current?}`)\n\n      // === FIRST AND LAST ===\n\n      mid <- TrafficLight.Amber\n      stdout.println(`First: ${#< mid}`)\n      stdout.println(`Last: ${#> mid}`)\n\n      // === SAFE CYCLING ===\n\n      item <- TrafficLight.Red\n      while item?\n        stdout.println(`Cycle: ${item}`)\n        item++","migrationContext":"Java: ordinal() + 1 with manual bounds checking, risk of ArrayIndexOutOfBounds, no built-in next/previous methods. Python: no built-in next or previous on Enum, must use list indexing with manual bounds. Rust: no built-in increment/decrement, must implement manually or use strum crate. Go: can increment iota integer but no bounds safety, wraps silently. C#: can cast to integer and increment but no bounds checking. Kotlin: ordinal + 1 with values() array, manual bounds required. EK9: ++ and -- with automatic boundary-to-unset semantics, completely safe.","keywords":["boundary","cycle","decrement","enum","enumeration","first","increment","last","migrate","navigate","next","previous","unset"],"primaryTopics":[],"typicalErrors":[{"error":"E50060","correct":"light++","incorrect":"light.next()","explanation":"EK9 enumerations do not have next() or previous() methods. Use the ++ operator to advance to the next value and -- to go to the previous value. These are mutating operators built into the enum type. See ek9 -h E50060 for details."}],"companions":[]}
{"id":221,"category":"Enumerations","question":"How can I compare an enum directly to a string in EK9?","url":"https://ek9.io/qa/QA0221.html","alternatePhrasings":["Can I use == to compare an enum to a string in EK9?","How does string comparison work with EK9 enumerations?","How do I construct an enum from a string safely in EK9?"],"answer":"EK9 enumerations support 7 string-parameter comparison operators: ==, <>, <, >, <=, >=, and <=>. You can also construct an enum from a string safely.\n\nDIRECT STRING COMPARISON\nCompare any enum variable directly to a string literal: season == \"Spring\" returns true if the enum holds Spring. No .name(), no .toString(), no conversion needed.\n\nALL SEVEN OPERATORS\nAll comparison operators work with strings: ==, <>, <, >, <=, >=, <=>. Ordering follows declaration order, just like enum-vs-enum comparison.\n\nSAFE STRING CONSTRUCTION\nSeason(\"Autumn\") returns a set enum if the string matches a declared value. Season(\"Monsoon\") returns an unset enum, not an exception.\n\nGUARD EXPRESSION PATTERN\nUse guard expressions for the cleanest pattern: 'if parsed <- Season(\"Summer\")' only enters the block when the enum is set. Combines construction and checking in one line.\n\nNO EXCEPTIONS\nIn Java, valueOf(\"bad\") throws IllegalArgumentException. In Python, Enum[\"bad\"] raises KeyError. In EK9, you simply get unset. No try-catch needed, just use a guard expression.\n\nSee Q219 for all auto-generated operators. See Q222 for unset propagation. See Q99 for basic enumeration.","ek9Example":"defines module qa.enums.stringcompare\n\n  defines type\n\n    Season\n      Spring\n      Summer\n      Autumn\n      Winter\n\n  defines program\n\n    EnumStringCompareDemo()\n      stdout <- Stdout()\n\n      season <- Season.Spring\n\n      // === DIRECT STRING COMPARISON ===\n\n      springName <- \"Spring\"\n      stdout.println(`Equals Spring: ${season == springName}`)\n      stdout.println(`Not Winter: ${season <> \"Winter\"}`)\n\n      // === ALL SEVEN STRING COMPARISON OPERATORS ===\n\n      stdout.println(`Less than Summer: ${season < \"Summer\"}`)\n      stdout.println(`Greater than Autumn: ${season > \"Autumn\"}`)\n      stdout.println(`Leq Spring: ${season <= springName}`)\n      stdout.println(`Geq Spring: ${season >= springName}`)\n      stdout.println(`Compare to Winter: ${season <=> \"Winter\"}`)\n\n      // === SAFE STRING CONSTRUCTION ===\n\n      valid <- Season(\"Autumn\")\n      stdout.println(`Valid: ${valid}, isSet: ${valid?}`)\n\n      invalid <- Season(\"Monsoon\")\n      stdout.println(`Invalid isSet: ${invalid?}`)\n\n      // === GUARD EXPRESSION WITH STRING CONSTRUCTION ===\n\n      if parsed <- Season(\"Summer\")\n        stdout.println(`Parsed: ${parsed}`)\n\n      if missing <- Season(\"Rainy\")\n        stdout.println(\"This will not print\")","migrationContext":"Java: valueOf() throws IllegalArgumentException for invalid strings, name() and toString() for conversion, no direct string comparison operators. Python: Enum[\"name\"] raises KeyError, must wrap in try/except. Rust: from_str requires implementing or deriving FromStr trait, returns Result. Go: manual string matching with switch or map lookup. C#: Enum.Parse() throws ArgumentException, Enum.TryParse() exists but returns bool. Kotlin: valueOf() throws IllegalArgumentException, entries.find{} for safe lookup. EK9: direct == comparison, safe string construction returns unset not exception.","keywords":["compare","comparison","convert","direct","enum","enumeration","exception","match","migrate","name","safe","string"],"primaryTopics":[],"typicalErrors":[{"error":"E50060","correct":"season == springName","incorrect":"season.name() == springName","explanation":"EK9 enumerations do not have a 'name()' method like Java enums. Use the == operator directly to compare an enum to a string. All seven comparison operators work with String arguments natively. See ek9 -h E50060 for details."}],"companions":[]}
{"id":222,"category":"Enumerations","question":"What happens when an EK9 enum is unset?","url":"https://ek9.io/qa/QA0222.html","alternatePhrasings":["How does EK9 handle null enum values?","What is the tri-state behaviour of EK9 enumerations?","How does unset propagation work with EK9 enums?"],"answer":"EK9 enumerations follow tri-state semantics: a variable can be set to a specific member, or it can be unset. There is no null, no exception, and no undefined behaviour.\n\nTHREE WAYS TO GET UNSET\nPriority() creates an unset enum. Priority(\"Invalid\") returns unset when the string does not match any member. Incrementing past the last member or decrementing before the first member also produces unset.\n\nUNSET PROPAGATION\nComparison with an unset enum returns an unset Boolean. String conversion of an unset enum returns an unset String. JSON conversion of an unset enum returns an unset JSON. The unset state flows through operations naturally.\n\nFIRST AND LAST ON UNSET\nThe #< (first) and #> (last) operators are type-level operations. They always return the first or last declared member, even when applied to an unset variable. This makes them safe anchors for restarting navigation.\n\nCHECKING WITH ?\nUse the ? operator to check if an enum is set. Guard expressions work naturally: 'if checked <- getStatus()' only enters the block when the result is set.\n\nSee Q29 for unset variables. See Q219 for all auto-generated operators. See Q220 for navigation. See Q221 for string comparison.","ek9Example":"defines module qa.enums.unset\n\n  defines type\n\n    Priority\n      Low\n      Medium\n      High\n      Critical\n\n  defines program\n\n    EnumUnsetDemo()\n      stdout <- Stdout()\n\n      // === THREE WAYS TO GET UNSET ===\n\n      fromDefault <- Priority()\n      stdout.println(`Default constructor isSet: ${fromDefault?}`)\n\n      fromBadString <- Priority(\"Urgent\")\n      stdout.println(`Bad string isSet: ${fromBadString?}`)\n\n      pastLast <- Priority.Critical\n      pastLast++\n      stdout.println(`Past last isSet: ${pastLast?}`)\n\n      // === UNSET PROPAGATION ===\n\n      unsetPriority <- Priority()\n      setPriority <- Priority.High\n\n      eqResult <- unsetPriority == setPriority\n      stdout.println(`Unset == Set isSet: ${eqResult?}`)\n\n      cmpResult <- unsetPriority <=> setPriority\n      stdout.println(`Unset <=> Set isSet: ${cmpResult?}`)\n\n      unsetStr <- $unsetPriority\n      stdout.println(`Unset string isSet: ${unsetStr?}`)\n\n      unsetHash <- #?unsetPriority\n      stdout.println(`Unset hashcode isSet: ${unsetHash?}`)\n\n      // === FIRST AND LAST STILL WORK ON UNSET ===\n\n      firstFromUnset <- #< unsetPriority\n      stdout.println(`First from unset: ${firstFromUnset}`)\n\n      lastFromUnset <- #> unsetPriority\n      stdout.println(`Last from unset: ${lastFromUnset}`)\n\n      // === CHECKING WITH ? ===\n\n      if setPriority?\n        stdout.println(`Set priority: ${setPriority}`)\n\n      if unsetPriority?\n        stdout.println(\"This will not print\")","migrationContext":"Java: throws NullPointerException on null enum reference, IllegalArgumentException on bad valueOf(). Python: raises ValueError on invalid Enum construction, None requires explicit null checks. Rust: Option<EnumType> requires explicit unwrapping with match or if-let, no propagation. Go: zero value is integer 0 which may silently represent a valid enum constant. C#: nullable enum exists but no propagation semantics, requires explicit null checks. Kotlin: nullable enum with ?. operator but no systematic unset propagation. EK9: systematic tri-state with natural propagation through all operators, check with ? when ready.","keywords":["absent","check","enum","enumeration","exception","null","propagation","safe","set","tri-state","unset"],"primaryTopics":[],"typicalErrors":[{"error":"E50060","correct":"eqResult <- unsetPriority == setPriority","incorrect":"eqResult <- unsetPriority.equals(setPriority)","explanation":"EK9 enumerations do not have an 'equals()' method. Use the == operator for equality comparison. The == operator handles unset values safely by producing an unset Boolean result. See ek9 -h E50060 for details."}],"companions":[]}
{"id":223,"category":"Enumerations","question":"How do I create a subset of enum values in EK9?","url":"https://ek9.io/qa/QA0223.html","alternatePhrasings":["What are constrained enumerations in EK9?","How do I restrict an enum to a subset of values?","Can I create a type-safe enum subset in EK9?"],"answer":"EK9 supports constrained enumerations that create a new type limited to a subset of values from a base enum. This is a compile-time type-safe mechanism, not a runtime collection.\n\nCONSTRAINED SYNTAX\nDeclare a constrained enum with 'as BaseEnum constrain as' followed by quoted values joined with 'or':\n  RedSuit as CardSuit constrain as\n    \"Hearts\" or \"Diamonds\"\nThis creates a NEW type RedSuit that can only hold Hearts or Diamonds.\n\nNOT A SUBTYPE\nConstrained enums are separate types, not subtypes of the base enum. You cannot assign a RedSuit to a CardSuit variable or vice versa. This is intentional: the type system guarantees that each variable holds exactly the values its type allows.\n\nCONSTRUCTION\nA bare constructor ASSERTS validity: RedSuit(CardSuit.Hearts) yields a set value, but a set value outside the subset is invalid - it PANICS at runtime, and a violating literal constant (e.g. RedSuit(\"Clubs\")) is the compile error E08260. To VALIDATE an untrusted base value without panicking, use the fallible factory .of(baseValue), which returns a set value when valid and an UNSET value when it violates the constraint: RedSuit().of(CardSuit.Hearts) is set, RedSuit().of(CardSuit.Clubs) is unset. Because .of(...) takes a base-enum value, parse a String into the base enum first: RedSuit().of(CardSuit(\"Hearts\")) is set, RedSuit().of(CardSuit(\"Clubs\")) is unset. Combine with guard expressions for clean checking: 'if parsed <- RedSuit().of(CardSuit(\"Diamonds\"))'.\n\nITERATION\nConstrained enums support iteration and streaming just like base enums: 'for suit in RedSuit' iterates over the constrained subset.\n\nOPERATORS\nConstrained enums inherit all comparison, string conversion, and query operators from the base enum. You can compare constrained values, check with ?, convert to string, and use first/last.\n\nSee Q99 for basic enumerations. See Q219 for auto-generated operators. See Q195 for generic constraints.","ek9Example":"defines module qa.enums.constrained\n\n  defines type\n\n    CardSuit\n      Hearts\n      Diamonds\n      Clubs\n      Spades\n\n    RedSuit as CardSuit constrain as\n      \"Hearts\" or \"Diamonds\"\n\n    BlackSuit as CardSuit constrain as\n      \"Clubs\" or \"Spades\"\n\n  defines program\n\n    EnumConstrainedDemo()\n      stdout <- Stdout()\n\n      // === CONSTRUCTION FROM BASE ENUM ===\n      // Use the fallible factory .of(baseValue) for validation: it returns\n      // a SET value when the base enum value satisfies the constraint and an\n      // UNSET value when it violates it - never panics. This is the pattern\n      // for validating untrusted/boundary values.\n\n      redCard <- RedSuit().of(CardSuit.Hearts)\n      stdout.println(`Red suit: ${redCard}, isSet: ${redCard?}`)\n\n      blackCard <- BlackSuit().of(CardSuit.Spades)\n      stdout.println(`Black suit: ${blackCard}, isSet: ${blackCard?}`)\n\n      // === CONSTRAINT ENFORCEMENT ===\n      // Clubs is not in RedSuit, Hearts is not in BlackSuit: .of(...) returns\n      // an UNSET value rather than panicking.\n\n      invalidRed <- RedSuit().of(CardSuit.Clubs)\n      stdout.println(`Clubs in RedSuit isSet: ${invalidRed?}`)\n\n      invalidBlack <- BlackSuit().of(CardSuit.Hearts)\n      stdout.println(`Hearts in BlackSuit isSet: ${invalidBlack?}`)\n\n      // === OPERATORS WORK ON CONSTRAINED ENUMS ===\n\n      red1 <- RedSuit(CardSuit.Hearts)\n      red2 <- RedSuit(CardSuit.Diamonds)\n      stdout.println(`Hearts < Diamonds: ${red1 < red2}`)\n      sameAsRed1 <- RedSuit(CardSuit.Hearts)\n      stdout.println(`Hearts == Hearts: ${red1 == sameAsRed1}`)\n      stdout.println(`String: ${red1}`)\n      stdout.println(`Hashcode: ${#?red1}`)\n\n      // === STRING VALIDATION VIA THE BASE ENUM AND .of(...) ===\n      // The fallible factory .of(...) takes a base-enum value, so parse the\n      // (untrusted) String into a CardSuit first, then validate against the\n      // constrained subset. A value outside the subset yields an UNSET result.\n\n      fromString <- RedSuit().of(CardSuit(\"Hearts\"))\n      stdout.println(`From string: ${fromString}, isSet: ${fromString?}`)\n\n      invalidString <- RedSuit().of(CardSuit(\"Clubs\"))\n      stdout.println(`Invalid string isSet: ${invalidString?}`)\n\n      // === GUARD EXPRESSION WITH CONSTRAINED ENUM ===\n\n      if parsed <- RedSuit().of(CardSuit(\"Diamonds\"))\n        stdout.println(`Parsed: ${parsed}`)\n\n      // === UNSET CONSTRAINED ENUM ===\n\n      unsetRed <- RedSuit()\n      stdout.println(`Unset RedSuit isSet: ${unsetRed?}`)\n\n      // === ITERATION OVER CONSTRAINED ENUM ===\n\n      for suit in RedSuit\n        stdout.println(`Red: ${suit}`)\n\n      // === FULL BASE ENUM STILL WORKS ===\n\n      allSuits <- cat CardSuit | collect as List of CardSuit\n      stdout.println(`All suits: ${length allSuits}`)","migrationContext":"Java: EnumSet.of(Hearts, Diamonds) is runtime only, no type safety, any CardSuit can be passed where EnumSet expected. Python: no equivalent, must use runtime validation. Rust: no equivalent, would need separate enum plus TryFrom conversion. Go: no equivalent, no enum type at all. Kotlin: no equivalent, sealed interface can approximate but verbose. C#: no equivalent, FlagsAttribute is bitwise not subset. TypeScript: union types are structural not nominal. EK9: compile-time type-safe enum subsets, constrained type is separate from base type.","keywords":["card","compile","constrain","define","enum","enumeration","restricted","safe","separate","subset","suit","type"],"primaryTopics":[],"typicalErrors":[{"error":"E50060","correct":"stdout.println(`Red suit: ${redCard}, isSet: ${redCard?}`)","incorrect":"stdout.display(`Red suit: ${redCard}, isSet: ${redCard?}`)","explanation":"Stdout does not have a display() method. The correct method is println(). Calling a non-existent method triggers E50060 — method not resolved. See ek9 -h E50060 for details."}],"companions":[]}
{"id":224,"category":"Enumerations","question":"How do I use enums with stream pipelines in EK9?","url":"https://ek9.io/qa/QA0224.html","alternatePhrasings":["Can I stream over enum values in EK9?","How do I iterate and transform enum values with pipelines?","How does cat work with EK9 enumerations?"],"answer":"EK9 enumerations are first-class stream sources. Use 'cat EnumType' to feed all declared values into a pipeline. No .values() conversion, no array wrapping.\n\nBASIC STREAMING\nStream all enum values to output: 'cat Season > stdout'. Or collect into a list: 'cat Season | collect as List of Season'.\n\nFILTER AND TRANSFORM\nUse filter and map in pipelines:\n  cat Season | filter by isWarm | collect as List of Season\nWhere isWarm is a function taking Season and returning Boolean.\n\nFOR-IN ITERATION\n'for season in Season' iterates all declared values in order. This is the simplest form when you need to process each value.\n\nCOLLECTION FROM ENUM\nCollect enums into lists for further processing:\n  allSeasons <- cat Season | collect as List of Season\n  length allSeasons returns the count of declared values.\n\nCOMBINING WITH FUNCTIONS\nPipe enum values through functions:\n  cat Season | map with describeFunc > stdout\nThis transforms each enum value through a function and outputs the results.\n\nSee Q59 for function pipelines. See Q99 for basic enumeration. See Q122 for collect as. See Q219 for auto-generated operators.","ek9Example":"defines module qa.enums.streams\n\n  defines type\n\n    Rank\n      Two\n      Three\n      Four\n      Five\n      Six\n      Seven\n      Eight\n      Nine\n      Ten\n      Jack\n      Queen\n      King\n      Ace\n\n    Suit\n      Hearts\n      Diamonds\n      Clubs\n      Spades\n\n  defines function\n\n    isFaceCard() as pure\n      -> r as Rank\n      <- face <- Boolean()\n      face: r > Rank.Ten\n\n    rankLabel() as pure\n      -> r as Rank\n      <- label <- String()\n      label: $r\n\n  defines program\n\n    EnumStreamsDemo()\n      stdout <- Stdout()\n\n      // === BASIC STREAMING ===\n\n      stdout.println(\"All suits:\")\n      cat Suit > stdout\n\n      // === COLLECT INTO LIST ===\n\n      allRanks <- cat Rank | collect as List of Rank\n      stdout.println(`Total ranks: ${length allRanks}`)\n\n      // === FILTER WITH FUNCTION ===\n\n      faceCards <- cat Rank | filter by isFaceCard | collect as List of Rank\n      stdout.println(`Face cards: ${length faceCards}`)\n\n      // === MAP WITH FUNCTION ===\n\n      stdout.println(\"Rank labels:\")\n      cat Rank | map with rankLabel > stdout\n\n      // === FOR-IN ITERATION ===\n\n      for suit in Suit\n        stdout.println(`Suit: ${suit}`)\n\n      // === STREAM SUITS AND COUNT ===\n\n      suitList <- cat Suit | collect as List of Suit\n      stdout.println(`Total suits: ${length suitList}`)","migrationContext":"Java: Arrays.stream(Season.values()) verbose, requires importing Arrays, values() returns mutable array copy. Python: list(Season) simpler but no pipeline, must chain with map/filter manually. Rust: needs strum::IntoEnumIterator external crate for iteration, .iter() not built-in. Go: must manually create slice of constants, no built-in iteration. Kotlin: entries.forEach or entries.filter, entries property since 1.9. C#: Enum.GetValues() returns Array, needs casting. EK9: 'cat Season' directly in pipeline, first-class stream source, zero conversion.","keywords":["cat","collect","enum","enumeration","filter","functional","iterate","list","map","pipeline","stream"],"primaryTopics":[],"typicalErrors":[{"error":"E50060","correct":"stdout.println(`Face cards: ${length faceCards}`)","incorrect":"stdout.display(`Face cards: ${length faceCards}`)","explanation":"Stdout does not have a display() method. The correct method is println(). Calling a non-existent method triggers E50060 — method not resolved. See ek9 -h E50060 for details."}],"companions":[]}
{"id":225,"category":"Enumerations","question":"How do I associate data or behaviour with enum values in EK9?","url":"https://ek9.io/qa/QA0225.html","alternatePhrasings":["Can EK9 enums have methods or fields?","How do I add behaviour to an EK9 enumeration?","What is the EK9 pattern for enum-associated data?"],"answer":"EK9 enumerations are pure value types with no methods or fields. Associate data using Dict with enum keys. Associate behaviour using functions that take the enum as a parameter.\n\nDICT FOR ASSOCIATED DATA\nUse a Dict with enum keys to map values to data:\n  labels <- {Severity.Low: \"Low priority\", Severity.High: \"Handle now\"}\nThis separates the enumeration from its associated data, keeping both independently testable.\n\nFUNCTIONS FOR BEHAVIOUR\nWrite functions that take the enum as a parameter:\n  severityLabel() as pure\n    -> s as Severity\n    <- label as String: switch s ...\nBehaviour lives in functions, not on the enum. Each function is independently testable.\n\nSWITCH EXPRESSIONS FOR MAPPING\nUse switch as an expression to map enum values to results:\n  label <- switch severity\n    <- rtn as String: String()\n    case Severity.Low\n      rtn: \"monitor\"\n    case Severity.High\n      rtn: \"escalate\"\nExhaustive switch guarantees all values are handled.\n\nWHY COMPOSITION\nAlan Perlis: 100 functions on one data structure beat 10 functions on 10 structures. Keeping enums pure means they stay serialisable, comparable, iterable, and JSON-convertible. Behaviour in functions means testable, composable, and replaceable logic.\n\nSee Q100 for enum vs Java comparison. See Q109 for composition pattern. See Q212 for composition over inheritance. See Q219 for auto-generated operators. See Q746 for migrating Swift enum associated values to EK9 composition.","ek9Example":"defines module qa.enums.composition\n\n  defines type\n\n    Severity\n      Info\n      Warning\n      Error\n      Critical\n\n  defines function\n\n    severityLabel() as pure\n      -> s as Severity\n      <- label as String: switch s\n        <- rtn as String: String()\n        case Severity.Info\n          rtn: \"Information - log and continue\"\n        case Severity.Warning\n          rtn: \"Warning - investigate soon\"\n        case Severity.Error\n          rtn: \"Error - fix required\"\n        case Severity.Critical\n          rtn: \"Critical - immediate action needed\"\n        default\n          rtn: \"No severity set\"\n\n    isActionRequired() as pure\n      -> s as Severity\n      <- required <- Boolean()\n      required: s >= Severity.Error\n\n  defines program\n\n    EnumCompositionDemo()\n      stdout <- Stdout()\n\n      // === FUNCTIONS FOR BEHAVIOUR ===\n\n      for s in Severity\n        stdout.println(severityLabel(s))\n\n      // === FUNCTIONS WITH COMPARISON ===\n\n      for s in Severity\n        stdout.println(`${s} requires action: ${isActionRequired(s)}`)\n\n      // === DICT FOR ASSOCIATED DATA ===\n\n      icons <- {Severity.Info: \"i\", Severity.Warning: \"!\", Severity.Error: \"X\", Severity.Critical: \"!!!\"}\n\n      for s in Severity\n        icon <- icons.getOrDefault(s, \"?\")\n        stdout.println(`${s}: ${icon}`)\n\n      // === SWITCH EXPRESSION FOR MAPPING ===\n\n      testSeverity <- Severity.Warning\n      category <- switch testSeverity\n        <- rtn as String: String()\n        case Severity.Info, Severity.Warning\n          rtn: \"monitor\"\n        case Severity.Error, Severity.Critical\n          rtn: \"escalate\"\n        default\n          rtn: \"unknown\"\n      stdout.println(`${testSeverity} category: ${category}`)","migrationContext":"Java: puts methods, fields, and constructors ON the enum (Planet with mass/radius/surfaceGravity()), creates tight coupling, harder to serialise. Python: adds methods to Enum class, can override __str__, mixes concerns. Rust: associates data per variant with enum fields, pattern matching extracts data. Go: methods on integer type, but enum is just integers. Kotlin: enum class with properties and abstract method implementations per entry. Swift: enum cases carry associated values per variant (case circle(radius: Double)), extracted via pattern matching, powerful but couples identity to data. C#: extension methods on enum type, or attributes for metadata. EK9: enums are pure values, use Dict for data association and functions for behaviour, separation of concerns.","keywords":["associate","behaviour","composition","design","dict","enum","enumeration","function","immutable","migrate","pattern","pure","separate","side-effect","swift"],"primaryTopics":[],"typicalErrors":[{"error":"E50060","correct":"icons.getOrDefault(s, \"?\")","incorrect":"icons.get(s)","explanation":"EK9 Dict does not have a 'get()' method. Use 'getOrDefault(key, default)' to retrieve values from a Dict, or use 'contains' to check for key existence first. See ek9 -h E50060 for details."}],"companions":[]}
{"id":226,"category":"Enumerations","question":"How does EK9 prevent common enum bugs?","url":"https://ek9.io/qa/QA0226.html","alternatePhrasings":["What enum bugs does EK9 eliminate at compile time?","Why are EK9 enums safer than in other languages?","How does EK9 protect against enum-related errors?"],"answer":"EK9 prevents entire categories of enum bugs that plague other languages. These protections are automatic and cannot be bypassed.\n\nEXHAUSTIVE SWITCH ENFORCEMENT\nWhen you switch on an enum using direct constant references, the compiler verifies ALL values are covered. Adding a new value to the enum forces you to handle it everywhere. No silent missing-case bugs.\n\nBOUNDARY TO UNSET\nIncrementing past the last value or decrementing before the first produces unset, not an index error or silent wraparound. Check with ? when you need to know.\n\nSAFE STRING CONSTRUCTION\nConstructing from an invalid string returns unset, not an exception. No try-catch overhead. No risk of uncaught IllegalArgumentException crashing production.\n\nIMMUTABLE CONSTANTS\nEnum values are immutable constants. You can compare and copy them but never modify the enum definition at runtime. No reflection tricks, no monkey-patching.\n\nNO INTEGER CASTING\nEK9 enums are not integers. You cannot accidentally pass 42 where a Season is expected. The type system prevents it at compile time.\n\nUNSET PROPAGATION\nOperations on unset enums produce unset results. No NullPointerException. No undefined behaviour. The unset state flows safely through your program until you check it.\n\nSee Q73 for exhaustive switch details. See Q99 for basic enumerations. See Q219 for all auto-generated operators. See Q222 for unset semantics.","ek9Example":"defines module qa.enums.safety\n\n  defines type\n\n    Status\n      Pending\n      Active\n      Paused\n      Complete\n\n  defines function\n\n    describeStatus() as pure\n      -> s as Status\n      <- description <- String()\n\n      //Exhaustive: compiler forces ALL values to be handled\n      switch s\n        case Status.Pending\n          description: \"waiting to start\"\n        case Status.Active\n          description: \"in progress\"\n        case Status.Paused\n          description: \"temporarily stopped\"\n        case Status.Complete\n          description: \"finished\"\n        default\n          description: \"no status set\"\n\n  defines program\n\n    EnumSafetyDemo()\n      stdout <- Stdout()\n\n      // === EXHAUSTIVE SWITCH: all values must be covered ===\n\n      for s in Status\n        stdout.println(`${s}: ${describeStatus(s)}`)\n\n      // === BOUNDARY TO UNSET: no index errors ===\n\n      last <- Status.Complete\n      last++\n      stdout.println(`Past last isSet: ${last?}`)\n\n      first <- Status.Pending\n      first--\n      stdout.println(`Before first isSet: ${first?}`)\n\n      // === SAFE STRING CONSTRUCTION: no exceptions ===\n\n      valid <- Status(\"Active\")\n      stdout.println(`Valid: ${valid}, isSet: ${valid?}`)\n\n      invalid <- Status(\"Running\")\n      stdout.println(`Invalid isSet: ${invalid?}`)\n\n      // === IMMUTABLE CONSTANTS: copy, do not modify ===\n\n      original <- Status.Active\n      copied <- Status(original)\n      stdout.println(`Original: ${original}, Copy: ${copied}`)\n\n      // === UNSET PROPAGATION: no null exceptions ===\n\n      unsetStatus <- Status()\n      result <- unsetStatus == Status.Active\n      stdout.println(`Unset comparison isSet: ${result?}`)\n\n      unsetStr <- $unsetStatus\n      stdout.println(`Unset string isSet: ${unsetStr?}`)","migrationContext":"C/C++: enums are integers, any integer value accepted, no type safety, missing switch cases are optional warnings only. Java: null enum throws NullPointerException, valueOf throws IllegalArgumentException, exhaustive switch only since Java 21 with sealed types. Python: no exhaustive checking in match/case for enums, Enum construction can raise ValueError. Go: iota constants are integers with zero type safety, any int passes. Rust: exhaustive match is strong, but no boundary-to-unset or safe string construction. Kotlin: when-expression exhaustive only when used as expression, nullable enums require explicit handling. C#: integer-backed, Enum.IsDefined for runtime checking only. EK9: compile-time exhaustive switch, boundary-to-unset, safe string construction, no integer casting, unset propagation.","keywords":["boundary","bug","compile","enum","enumeration","exhaustive","immutable","prevent","protection","safety","switch"],"primaryTopics":[],"typicalErrors":[{"error":"E50060","correct":"valid <- Status(\"Active\")","incorrect":"valid <- Status.valueOf(\"Active\")","explanation":"EK9 enumerations do not have a 'valueOf()' method like Java enums. Use the String constructor 'Status(\"Active\")' for safe string construction. Invalid strings return unset instead of throwing exceptions. See ek9 -h E50060 for details."}],"companions":[]}
{"id":227,"category":"Dependency Injection","question":"How does EK9 validate dependency injection at compile time?","url":"https://ek9.io/qa/QA0227.html","alternatePhrasings":["Does EK9 catch DI errors before runtime?","How does EK9 prevent missing dependency errors at compile time?","What DI validations does the EK9 compiler perform?","How does inversion of control work in EK9?"],"answer":"EK9 validates ALL dependency injection at compile time. If your program compiles, every injection point is guaranteed to be satisfied. There are zero runtime DI failures.\n\nFOUR COMPILE-TIME VALIDATIONS\nThe compiler performs four checks on every application definition:\n1. Completeness: Every injection field ('!' suffix) must have a matching registration\n2. Ordering: Dependencies must be registered before dependents\n3. Cycle detection: Circular dependencies are rejected with error E08190\n4. Field count: Components with excessive injection fields are flagged with E11040\n\nCONTRAST WITH SPRING/GUICE\nIn Java Spring, a missing @Bean causes NoSuchBeanDefinitionException at runtime, sometimes minutes into startup. In Guice, a missing binding produces CreationException. In EK9, you find out during compilation, before any code runs.\n\nWHY THIS MATTERS\nRuntime DI failures are among the most frustrating bugs. They appear only when a specific code path is exercised, often in production. EK9 eliminates this entire category of defect by moving validation to compile time.\n\nWORKING EXAMPLE\nThe code below demonstrates a correctly wired application. The compiler has verified that Logger is registered before Formatter, both are registered before MessageService (which injects them), and the program's injection point is satisfied.\n\nSee Q111 for component basics. See Q117 for singleton lifecycle. See Q228 for registration ordering. See Q229 for circular dependency detection. See Q230 for missing registration errors. See Q310 for compile-time quality philosophy.\n\nSee Q324 for @Autowired equivalent. See Q332 for runtime overhead comparison.\n\nSee Q666 for injection pure context. See Q673 for missing registration. See Q675 for transitive validation.","ek9Example":"defines module qa.di.compile.time\n\n  defines component\n\n    Logger as abstract\n      log() as abstract\n        -> message as String\n        <- output as String?\n\n      default operator ?\n\n    ConsoleLogger is Logger\n      override log()\n        -> message as String\n        <- output as String: \"LOG: \" + message\n\n      default operator ?\n\n    Formatter as abstract\n      format() as abstract\n        -> text as String\n        <- result as String?\n\n      default operator ?\n\n    UpperFormatter is Formatter\n      override format()\n        -> text as String\n        <- result as String: text.upperCase()\n\n      default operator ?\n\n    MessageService as abstract\n      greet() as abstract\n        -> name as String\n        <- greeting as String?\n\n      default operator ?\n\n    SimpleMessageService is MessageService\n      logger as Logger!\n      formatter as Formatter!\n\n      override greet()\n        -> name as String\n        <- greeting <- String()\n        formatted <- formatter.format(\"Hello \" + name)\n        greeting: logger.log(formatted)\n\n      default operator ?\n\n  defines application\n\n    ValidatedApp\n      register ConsoleLogger() as Logger\n      register UpperFormatter() as Formatter\n      register SimpleMessageService() as MessageService\n\n  defines program\n\n    CompileTimeDiDemo() with application of ValidatedApp\n      stdout <- Stdout()\n\n      // === ALL INJECTION VALIDATED AT COMPILE TIME ===\n\n      service as MessageService!\n\n      result <- service.greet(\"World\")\n      stdout.println(result)\n\n      stdout.println(\"All injections verified by compiler\")","migrationContext":"Java Spring: NoSuchBeanDefinitionException at runtime, @Conditional can hide missing beans, circular dependency detection only at startup. Guice: CreationException at Injector creation, JIT bindings can mask issues. .NET: InvalidOperationException during service resolution, runtime-only. Python: no compile-time DI validation at all. Go: no built-in DI. Rust: no built-in DI. EK9: ALL DI validated at compile time, zero runtime failures possible.","keywords":["circular","compile","completeness","guarantee","inject","injection","inversion","ioc","migrate","ordering","safety","validate"],"primaryTopics":["dependency injection","DI","inject","inversion of control","IoC","autowired"],"typicalErrors":[{"error":"E08150","correct":"logger as Logger!","incorrect":"logger as ConsoleLogger!","explanation":"Injection fields must use abstract types, not concrete implementations. Only abstract components can be injection targets. See ek9 -h E08150 for details."},{"error":"E50060","correct":"result <- service.greet(\"World\")","incorrect":"result <- service.greet(\"World\").toUpperCase()","explanation":"String has no toUpperCase() method in EK9. Use upperCase() instead. See ek9 -h E50060 for details."},{"error":"E08210","correct":"register ConsoleLogger() as Logger","incorrect":"register UpperFormatter() as Formatter","explanation":"If the register statement for Logger were removed, the injection field 'logger as Logger!' in SimpleMessageService would have no matching registration and the compiler would reject it. See ek9 -h E08210 for details."}],"companions":[]}
{"id":228,"category":"Dependency Injection","question":"Why does registration order matter in EK9 applications?","url":"https://ek9.io/qa/QA0228.html","alternatePhrasings":["What order should I register components in EK9?","Does the sequence of register statements matter in EK9?","How do I order dependencies in an EK9 application?"],"answer":"In EK9, the order of 'register' statements in an application definition matters. Dependencies must be registered BEFORE the components that depend on them.\n\nWHY ORDER MATTERS\nEK9 processes registrations sequentially. When the compiler encounters a component with injection fields, it verifies those dependencies are already registered. If component B injects component A, then A must appear before B in the register list.\n\nCORRECT ORDERING\nFor a chain A -> B -> C (C depends on B, B depends on A):\n  register ConcreteA() as AbstractA    <- no dependencies, register first\n  register ConcreteB() as AbstractB    <- depends on A, register second\n  register ConcreteC() as AbstractC    <- depends on B, register third\n\nWHAT HAPPENS WITH WRONG ORDER\nIf you register B before A, the compiler reports an error because B's injection field for A cannot be satisfied at that point in the registration sequence.\n\nDESIGN RATIONALE\nExplicit ordering makes the dependency graph visible in the application definition. You can read the register list top-to-bottom and understand the initialization order. No hidden runtime sorting or lazy resolution.\n\nSee Q111 for component basics. See Q227 for compile-time validation overview. See Q229 for circular dependency detection. See Q232 for transitive component chains.\n\nSee Q326 for @Bean/@Configuration equivalent.","ek9Example":"defines module qa.di.ordering\n\n  defines component\n\n    Logger as abstract\n      log() as abstract\n        -> message as String\n        <- output as String?\n\n      default operator ?\n\n    ConsoleLogger is Logger\n      override log()\n        -> message as String\n        <- output as String: \"[LOG] \" + message\n\n      default operator ?\n\n    Formatter as abstract\n      format() as abstract\n        -> text as String\n        <- result as String?\n\n      default operator ?\n\n    SimpleFormatter is Formatter\n      logger as Logger!\n\n      override format()\n        -> text as String\n        <- result <- String()\n        result: logger.log(text.upperCase())\n\n      default operator ?\n\n    NotificationService as abstract\n      notify() as abstract\n        -> message as String\n        <- result as String?\n\n      default operator ?\n\n    EmailNotifier is NotificationService\n      formatter as Formatter!\n\n      override notify()\n        -> message as String\n        <- result <- String()\n        result: formatter.format(\"NOTIFY: \" + message)\n\n      default operator ?\n\n  defines application\n\n    OrderedApp\n      // Dependencies registered BEFORE dependents\n      register ConsoleLogger() as Logger\n      register SimpleFormatter() as Formatter\n      register EmailNotifier() as NotificationService\n\n  defines program\n\n    OrderingDemo() with application of OrderedApp\n      stdout <- Stdout()\n\n      // === CORRECT ORDER: Logger -> Formatter -> NotificationService ===\n\n      notifier as NotificationService!\n\n      result <- notifier.notify(\"System ready\")\n      stdout.println(result)\n\n      stdout.println(\"Three-layer chain wired in correct order\")","migrationContext":"Java Spring: registration order does not matter, container resolves lazily. Guice: binding order irrelevant, dependency graph resolved at injector creation. .NET: registration order irrelevant, resolved on demand. Python: manual wiring, order depends on programmer. Go: manual wiring, initialization order explicit. Rust: no built-in DI. EK9: explicit registration order required, dependencies before dependents, compiler enforces correct sequencing.","keywords":["after","application","before","chain","dependency","explicit","initialization","inject","order","register","sequence"],"primaryTopics":[],"typicalErrors":[{"error":"E08200","correct":"register ConsoleLogger() as Logger\n      register SimpleFormatter() as Formatter","incorrect":"register SimpleFormatter() as Formatter\n      register ConsoleLogger() as Logger","explanation":"SimpleFormatter injects Logger, so Logger must be registered before SimpleFormatter. Reversing the order means the dependency is not yet available when the compiler processes SimpleFormatter. See ek9 -h E08200 for details."},{"error":"E08150","correct":"logger as Logger!","incorrect":"logger as ConsoleLogger!","explanation":"Injection fields must reference abstract component types, not concrete implementations. The application registers the concrete type; the field declares the abstract type. See ek9 -h E08150 for details."}],"companions":[]}
{"id":229,"category":"Dependency Injection","question":"How does EK9 prevent circular dependencies?","url":"https://ek9.io/qa/QA0229.html","alternatePhrasings":["What happens with circular DI in EK9?","Does EK9 detect dependency cycles at compile time?","How do I fix circular dependencies in EK9?"],"answer":"EK9 detects circular dependencies at compile time using depth-first search on the dependency graph. If component A depends on B and B depends on A, the compiler reports error E08190 and refuses to compile.\n\nWHAT E08190 CATCHES\nThe compiler traces every injection chain. If it finds a cycle of any length (A->B->A, or A->B->C->A, etc.), it reports E08190 with the cycle path so you know exactly which components are involved.\n\nFIXING CIRCULAR DEPENDENCIES\nThe solution is always the same: break the cycle by extracting the shared responsibility into a separate component. If A and B both need each other, extract the shared logic into C, then have both A and B depend on C.\n\nBEFORE (circular, would not compile):\n  ServiceA injects ServiceB\n  ServiceB injects ServiceA\n\nAFTER (refactored, compiles correctly):\n  SharedLogic has no injections\n  ServiceA injects SharedLogic\n  ServiceB injects SharedLogic\n\nCONTRAST WITH SPRING\nSpring tries to resolve circular dependencies with proxy objects and lazy initialization, which leads to subtle bugs, partial initialization, and startup order sensitivity. EK9 simply rejects cycles.\n\nDESIGN PRINCIPLE\nCircular dependencies indicate a design flaw. By rejecting them at compile time, EK9 forces clean layered architecture from the start.\n\nSee Q228 for registration ordering. See Q227 for compile-time validation overview. See Q232 for clean multi-layer dependency chains.\n\nSee Q329 for Guice comparison.","ek9Example":"defines module qa.di.circular\n\n  defines component\n\n    // === CORRECT PATTERN: Shared logic extracted to break potential cycle ===\n\n    SharedFormatter as abstract\n      format() as abstract\n        -> text as String\n        <- result as String?\n\n      default operator ?\n\n    PlainFormatter is SharedFormatter\n      override format()\n        -> text as String\n        <- result as String: `[${text}]`\n\n      default operator ?\n\n    UserService as abstract\n      getUser() as abstract\n        -> id as Integer\n        <- name as String?\n\n      default operator ?\n\n    SimpleUserService is UserService\n      formatter as SharedFormatter!\n\n      override getUser()\n        -> id as Integer\n        <- name <- String()\n        name: formatter.format(\"User-\" + $id)\n\n      default operator ?\n\n    OrderService as abstract\n      getOrder() as abstract\n        -> id as Integer\n        <- details as String?\n\n      default operator ?\n\n    SimpleOrderService is OrderService\n      formatter as SharedFormatter!\n\n      override getOrder()\n        -> id as Integer\n        <- details <- String()\n        details: formatter.format(\"Order-\" + $id)\n\n      default operator ?\n\n  defines application\n\n    CleanApp\n      // SharedFormatter registered first (no dependencies)\n      // Both services depend on formatter, NOT on each other\n      register PlainFormatter() as SharedFormatter\n      register SimpleUserService() as UserService\n      register SimpleOrderService() as OrderService\n\n  defines program\n\n    CircularDepsDemo() with application of CleanApp\n      stdout <- Stdout()\n\n      // === CLEAN DESIGN: no circular dependencies ===\n\n      users as UserService!\n      orders as OrderService!\n\n      stdout.println(users.getUser(42))\n      stdout.println(orders.getOrder(99))\n\n      stdout.println(\"Both services share formatter without circular dependency\")","migrationContext":"Java Spring: circular dependencies resolved via proxies and @Lazy, deprecated since Spring 6, often indicates design flaw. Guice: circular dependency causes ProvisionException, except with Provider indirection. .NET: circular DI causes InvalidOperationException at runtime. Python: circular imports cause ImportError. Go: no built-in DI, circular package imports are compile errors. Rust: no built-in DI. EK9: E08190 compile-time error, DFS cycle detection, forces clean architecture.","keywords":["E08190","break","circular","compile","cycle","dependency","design","detect","graph","inject","migrate","refactor"],"primaryTopics":[],"typicalErrors":[{"error":"E50001","correct":"formatter as SharedFormatter!","incorrect":"orderService as OrderService!","explanation":"If SimpleUserService injected OrderService and SimpleOrderService injected UserService, it would create a circular dependency cycle (A->B->A) that the compiler detects and rejects. The correct pattern extracts shared logic into a separate component. See ek9 -h E50001 for details."},{"error":"E08150","correct":"formatter as SharedFormatter!","incorrect":"formatter as PlainFormatter!","explanation":"Injection fields must use abstract component types. Injecting the concrete PlainFormatter directly violates the abstract injection requirement. See ek9 -h E08150 for details."}],"companions":[]}
{"id":230,"category":"Dependency Injection","question":"What happens if I forget to register a dependency in EK9?","url":"https://ek9.io/qa/QA0230.html","alternatePhrasings":["Does EK9 catch missing component registrations?","What error do I get for unregistered dependencies in EK9?","How does EK9 handle missing DI bindings?"],"answer":"If you forget to register a dependency, the EK9 compiler reports a compile-time error identifying the unsatisfied injection point. Your code will not compile until every injection field has a matching registration.\n\nCOMPILE-TIME VS RUNTIME\nIn Java Spring, forgetting an @Bean produces NoSuchBeanDefinitionException at runtime, potentially minutes into application startup. In EK9, the compiler catches this immediately during compilation.\n\nWHAT THE ERROR TELLS YOU\nThe compiler identifies:\n- The component or program with the unsatisfied injection field\n- The type that was expected to be registered\n- The application definition where the registration is missing\nThis gives you everything needed to fix the problem in seconds.\n\nCOMPLETENESS CHECK\nThe compiler walks every injection field in every component and program linked to an application. For each '!' field, it verifies a matching 'register X() as Y' exists where Y matches the field type.\n\nWORKING EXAMPLE\nThe code below shows a complete, correctly wired application. All three components are registered, and the program's injection point is satisfied. If any registration were removed, the compiler would report the error before any code runs.\n\nSee Q111 for component basics. See Q227 for compile-time validation. See Q228 for registration ordering.\n\nSee Q111 for components. See Q227 for compile-time DI. See Q228 for registration ordering.","ek9Example":"defines module qa.di.missing\n\n  defines component\n\n    Cache as abstract\n      get() as abstract\n        -> key as String\n        <- value as String?\n\n      default operator ?\n\n    InMemoryCache is Cache\n      override get()\n        -> key as String\n        <- value as String: \"cached:\" + key\n\n      default operator ?\n\n    Repository as abstract\n      find() as abstract\n        -> id as Integer\n        <- result as String?\n\n      default operator ?\n\n    CachedRepository is Repository\n      cache as Cache!\n\n      override find()\n        -> id as Integer\n        <- result <- String()\n        result: cache.get(\"item-\" + $id)\n\n      default operator ?\n\n    Controller as abstract\n      handle() as abstract\n        -> request as String\n        <- response as String?\n\n      default operator ?\n\n    ItemController is Controller\n      repo as Repository!\n\n      override handle()\n        -> request as String\n        <- response <- String()\n        response: repo.find(1)\n\n      default operator ?\n\n  defines application\n\n    CompleteApp\n      // All three registrations present - compiler verifies completeness\n      register InMemoryCache() as Cache\n      register CachedRepository() as Repository\n      register ItemController() as Controller\n\n  defines program\n\n    MissingRegDemo() with application of CompleteApp\n      stdout <- Stdout()\n\n      // === ALL REGISTRATIONS SATISFIED ===\n\n      controller as Controller!\n\n      response <- controller.handle(\"/items/1\")\n      stdout.println(response)\n\n      stdout.println(\"Every injection point satisfied at compile time\")","migrationContext":"Java Spring: NoSuchBeanDefinitionException at runtime, UnsatisfiedDependencyException for @Autowired. Guice: ConfigurationException for missing bindings at Injector.getInstance(). .NET: InvalidOperationException 'No service for type'. Python: KeyError or AttributeError at runtime. Go: nil pointer at runtime. Rust: compile-time via generics, but no built-in DI. EK9: compile-time error identifies exact missing registration, zero runtime failures.","keywords":["binding","catch","compile","completeness","dependency","error","forget","inject","migrate","missing","registration","unsatisfied","validate"],"primaryTopics":[],"typicalErrors":[{"error":"E08210","correct":"register InMemoryCache() as Cache\n      register CachedRepository() as Repository\n      register ItemController() as Controller","incorrect":"register CachedRepository() as Repository\n      register ItemController() as Controller","explanation":"Removing the Cache registration leaves CachedRepository's injection field 'cache as Cache!' unsatisfied. The compiler rejects programs where any injection point lacks a matching registration. See ek9 -h E08210 for details."},{"error":"E50060","correct":"response <- controller.handle(\"/items/1\")","incorrect":"response <- controller.handle(\"/items/1\").toUpperCase()","explanation":"String has no toUpperCase() method in EK9. Use upperCase() instead. See ek9 -h E50060 for details."}],"companions":[]}
{"id":231,"category":"Dependency Injection","question":"How do programs connect to applications for dependency injection?","url":"https://ek9.io/qa/QA0231.html","alternatePhrasings":["What does 'with application of' mean in EK9?","Can multiple programs share one application in EK9?","Do all EK9 programs need an application?","How do I use the application construct in EK9?"],"answer":"Programs declare their dependency source using 'with application of AppName'. This links the program to a specific application definition that provides its injected dependencies.\n\nBASIC LINKING\nA program declares its application after the parameter list:\n  MyProgram() with application of MyApp\n    service as ServiceType!\nThe compiler verifies that MyApp registers a concrete ServiceType.\n\nMULTIPLE PROGRAMS, ONE APPLICATION\nSeveral programs can link to the same application. Each program gets its own injection of the registered components:\n  ProgramA() with application of SharedApp\n    svc as ServiceType!\n  ProgramB() with application of SharedApp\n    svc as ServiceType!\n\nPROGRAMS WITHOUT DI\nPrograms that do not need injection simply omit 'with application of':\n  SimpleProg()\n    stdout <- Stdout()\n    stdout.println(\"No DI needed\")\nStdout is a built-in, not an injected component.\n\nWHEN TO USE APPLICATIONS\nUse 'with application of' when your program needs components, services, or other registered dependencies. Programs that only use built-in types and local variables do not need an application.\n\nSee Q111 for component basics. See Q117 for singleton lifecycle. See Q227 for compile-time validation.\n\nSee Q326 for @Bean equivalent. See Q327 for Spring Boot comparison.\n\nSee Q672 for program-application link details.","ek9Example":"defines module qa.di.program.app\n\n  defines component\n\n    Greeter as abstract\n      greet() as abstract\n        -> name as String\n        <- message as String?\n\n      default operator ?\n\n    FriendlyGreeter is Greeter\n      override greet()\n        -> name as String\n        <- message as String: `Hello, ${name}!`\n\n      default operator ?\n\n  defines application\n\n    SharedApp\n      register FriendlyGreeter() as Greeter\n\n  defines program\n\n    // === PROGRAM WITH APPLICATION: has injection ===\n\n    ProgramWithDI() with application of SharedApp\n      stdout <- Stdout()\n\n      greeter as Greeter!\n      stdout.println(greeter.greet(\"Alice\"))\n\n    // === SECOND PROGRAM: same application ===\n\n    AnotherProgram() with application of SharedApp\n      stdout <- Stdout()\n\n      greeter as Greeter!\n      stdout.println(greeter.greet(\"Bob\"))\n\n    // === PROGRAM WITHOUT APPLICATION: no injection needed ===\n\n    StandaloneProgram()\n      stdout <- Stdout()\n\n      // No injection fields, no application needed\n      stdout.println(\"Running without DI\")","migrationContext":"Java Spring: @SpringBootApplication auto-scans, no explicit linking per class. Guice: modules bound to Injector, no per-program declaration. .NET: IServiceCollection configured in Startup, all classes share one container. Python: no language-level DI linking. Go: no language-level DI. Rust: no language-level DI. EK9: explicit 'with application of' per program, multiple programs can share one application, programs without DI omit the clause entirely.","keywords":["application","connect","construct","declare","defines","inject","injection","link","migrate","program","share","wiring"],"primaryTopics":[],"typicalErrors":[{"error":"E50060","correct":"stdout.println(greeter.greet(\"Alice\"))","incorrect":"stdout.println(greeter.greet(\"Alice\").toUpperCase())","explanation":"String has no toUpperCase() method in EK9. Use upperCase() instead. See ek9 -h E50060 for details."},{"error":"E08150","correct":"greeter as Greeter!","incorrect":"greeter as FriendlyGreeter!","explanation":"Injection fields must use abstract component types. Injecting the concrete FriendlyGreeter directly bypasses the abstraction that enables substitution. See ek9 -h E08150 for details."}],"companions":[]}
{"id":232,"category":"Dependency Injection","question":"How do components inject other components in EK9?","url":"https://ek9.io/qa/QA0232.html","alternatePhrasings":["Can EK9 components depend on other components?","How do transitive dependency chains work in EK9?","How does field injection work between components?"],"answer":"Components inject other components using the '!' suffix on fields, exactly like programs inject components. The compiler resolves these transitive chains at compile time.\n\nFIELD INJECTION IN COMPONENTS\nA concrete component can declare injection fields:\n  ConcreteService is AbstractService\n    repo as Repository!\n    override doWork()\n      <- rtn as String: repo.find(1)\nThe '!' on 'repo' tells the compiler this field is injected, not constructed locally.\n\nTRANSITIVE CHAINS\nMulti-layer dependency chains are common:\n  Repository depends on nothing\n  Service depends on Repository\n  Controller depends on Service\nThe compiler traces the entire chain and verifies every link is registered.\n\nREGISTRATION ORDER\nRegister in dependency order (leaves first, roots last):\n  register ConcreteRepo() as Repository\n  register ConcreteService() as Service\n  register ConcreteController() as Controller\n\nALL COMPILE-TIME\nEvery field injection in every component is verified before code runs. The compiler sees the full dependency graph across all components and programs.\n\nSee Q228 for registration ordering. See Q111 for component basics. See Q227 for compile-time validation.","ek9Example":"defines module qa.di.chains\n\n  defines component\n\n    // === LAYER 1: Repository (no dependencies) ===\n\n    Repository as abstract\n      find() as abstract\n        -> id as Integer\n        <- result as String?\n\n      default operator ?\n\n    InMemoryRepo is Repository\n      override find()\n        -> id as Integer\n        <- result as String: \"Record-\" + $id\n\n      default operator ?\n\n    // === LAYER 2: Service (depends on Repository) ===\n\n    DataService as abstract\n      process() as abstract\n        -> id as Integer\n        <- output as String?\n\n      default operator ?\n\n    DefaultDataService is DataService\n      repo as Repository!\n\n      override process()\n        -> id as Integer\n        <- output <- String()\n        raw <- repo.find(id)\n        output: \"Processed: \" + raw\n\n      default operator ?\n\n    // === LAYER 3: Controller (depends on Service) ===\n\n    ApiController as abstract\n      handle() as abstract\n        -> id as Integer\n        <- response as String?\n\n      default operator ?\n\n    DefaultApiController is ApiController\n      service as DataService!\n\n      override handle()\n        -> id as Integer\n        <- response <- String()\n        result <- service.process(id)\n        response: \"Response: \" + result\n\n      default operator ?\n\n  defines application\n\n    ChainedApp\n      // Register in dependency order: leaves first\n      register InMemoryRepo() as Repository\n      register DefaultDataService() as DataService\n      register DefaultApiController() as ApiController\n\n  defines program\n\n    ComponentChainDemo() with application of ChainedApp\n      stdout <- Stdout()\n\n      // === THREE-LAYER INJECTION CHAIN ===\n\n      controller as ApiController!\n\n      response <- controller.handle(42)\n      stdout.println(response)\n\n      stdout.println(\"Repository -> Service -> Controller chain resolved at compile time\")","migrationContext":"Java Spring: @Autowired field injection between @Component classes, resolved at runtime via container. Guice: @Inject field injection, resolved at Injector creation. .NET: constructor injection between registered services. Python: no built-in component injection. Go: manual struct composition. Rust: no built-in DI. EK9: '!' field injection between components, transitive chains resolved at compile time, registration order enforced.","keywords":["chain","component","controller","depend","field","inject","injection","layer","repository","service","transitive"],"primaryTopics":[],"typicalErrors":[{"error":"E08150","correct":"repo as Repository!","incorrect":"repo as InMemoryRepo!","explanation":"Injection fields must use abstract types. Concrete types like InMemoryRepo cannot be injection targets because DI resolves through abstract-to-concrete registration. See ek9 -h E08150 for details."}],"companions":[]}
{"id":233,"category":"Dependency Injection","question":"Can dynamic classes use dependency injection in EK9?","url":"https://ek9.io/qa/QA0233.html","alternatePhrasings":["Do anonymous trait implementations support injection in EK9?","How does DI work with inline classes in EK9?","Can I inject components into dynamic classes?"],"answer":"Dynamic classes (anonymous trait implementations) can have injection fields. The compiler follows the call graph from programs through functions to find all dynamic class instantiations and validates their injection fields.\n\nDYNAMIC CLASS WITH INJECTION\nA dynamic class created with '() with trait of' can declare '!' fields:\n  handler <- () with trait of Processable\n    logger as Logger!\n    override process()\n      <- rtn as String: logger.log(\"processing\")\nThe Logger injection is resolved from the application context.\n\nCALL GRAPH ANALYSIS\nThe compiler traces the execution path from the program through any functions that create dynamic classes. If a function creates a dynamic class with injection fields, those fields must be satisfiable from the program's application.\n\nWHY THIS IS UNIQUE\nMost DI frameworks only support injection on named, registered classes. EK9 extends injection to anonymous inline implementations, which is powerful for callbacks, handlers, and one-off implementations that still need access to registered services.\n\nCOMPILE-TIME SAFETY\nAs with all EK9 DI, dynamic class injection is validated at compile time. If the required component is not registered, the compiler reports the error.\n\nSee Q227 for compile-time validation. See Q111 for component basics. See Q115 for dynamic class basics. See Q234 for component lifecycle.","ek9Example":"defines module qa.di.dynamic\n\n  defines trait\n\n    Processor\n      process() as abstract\n        -> input as String\n        <- output as String?\n\n      override operator ? as pure\n        <- rtn as Boolean: true\n\n  defines component\n\n    Logger as abstract\n      log() as abstract\n        -> message as String\n        <- result as String?\n\n      default operator ?\n\n    ConsoleLogger is Logger\n      override log()\n        -> message as String\n        <- result as String: \"LOG: \" + message\n\n      default operator ?\n\n  defines function\n\n    createProcessor()\n      <- handler as Processor?\n\n      // === DYNAMIC CLASS WITH INJECTION ===\n      handler: () with trait of Processor\n        logger as Logger!\n\n        override process()\n          -> input as String\n          <- output <- String()\n          output: logger.log(input)\n\n        default operator ?\n\n  defines application\n\n    DynamicApp\n      register ConsoleLogger() as Logger\n\n  defines program\n\n    DynamicInjectionDemo() with application of DynamicApp\n      stdout <- Stdout()\n\n      // === FUNCTION CREATES DYNAMIC CLASS WITH INJECTION ===\n\n      handler <- createProcessor()\n      result <- handler.process(\"test message\")\n      stdout.println(result)\n\n      stdout.println(\"Dynamic class injection validated at compile time\")","migrationContext":"Java: anonymous inner classes cannot use @Autowired, must receive dependencies via constructor or enclosing scope. Guice: no injection into anonymous classes. .NET: no injection into anonymous types. Python: no built-in DI for anonymous objects. Go: no anonymous classes. Rust: no anonymous classes with DI. EK9: dynamic classes with '() with trait of' support '!' injection fields, compiler traces call graph to validate.","keywords":["anonymous","call","callback","capture","class","closure","dynamic","graph","handler","inject","injection","inline","trait"],"primaryTopics":[],"typicalErrors":[{"error":"E08150","correct":"logger as Logger!","incorrect":"logger as ConsoleLogger!","explanation":"Even in dynamic classes, injection fields must reference abstract component types. The concrete type is determined by the application registration, not the injection field declaration. See ek9 -h E08150 for details."},{"error":"E07160","correct":"register ConsoleLogger() as Logger","incorrect":"","explanation":"Removing the only registration from the application leaves it empty, requiring at least one implementation to be provided. See ek9 -h E07160 for details."}],"companions":[]}
{"id":234,"category":"Dependency Injection","question":"What is the lifecycle of injected components in EK9?","url":"https://ek9.io/qa/QA0234.html","alternatePhrasings":["How are EK9 components created and destroyed?","What are the DI lifecycle phases in EK9?","Does EK9 support different component scopes like Spring?"],"answer":"EK9 components follow a simple three-phase lifecycle: prepare, execute, decommission. There is only one scope: singleton for the program's lifetime.\n\nPREPARE PHASE\nWhen a program starts, the application's _prepare() method creates all registered components in registration order. Dependencies are created before dependents, so injection fields are satisfied as each component is initialized.\n\nEXECUTE PHASE\nThe program body runs with all components fully initialized. Every injection point references the same instance throughout the program's execution.\n\nDECOMMISSION PHASE\nWhen the program completes, _decommission() releases components in REVERSE registration order. Dependents are released before their dependencies, ensuring no component uses a decommissioned dependency.\n\nSINGLETON ONLY\nEK9 has one scope: singleton for the program lifetime. There are no request, session, or prototype scopes. This simplicity eliminates an entire class of scope-related bugs.\n\nCONTRAST WITH SPRING\nSpring has singleton, prototype, request, session, and application scopes. Each scope adds complexity and potential for scope mismatch bugs (e.g., injecting a request-scoped bean into a singleton). EK9 avoids this entirely.\n\nWHY SINGLETON ONLY\nMost DI complexity comes from managing multiple scopes. Programs in EK9 are designed to be focused units. If you need different lifecycle management, use explicit construction within the program body.\n\nSee Q117 for singleton lifecycle. See Q111 for component basics. See Q227 for compile-time validation. See Q231 for program-application linking.\n\nSee Q330 for singleton-only scope rationale. See Q332 for performance comparison.","ek9Example":"defines module qa.di.lifecycle\n\n  defines component\n\n    Logger as abstract\n      log() as abstract\n        -> message as String\n        <- output as String?\n\n      default operator ?\n\n    AppLogger is Logger\n      override log()\n        -> message as String\n        <- output as String: `[${message}]`\n\n      default operator ?\n\n    DataStore as abstract\n      save() as abstract\n        -> content as String\n        <- result as String?\n\n      default operator ?\n\n    InMemoryStore is DataStore\n      logger as Logger!\n\n      override save()\n        -> content as String\n        <- result <- String()\n        result: logger.log(\"Saved: \" + content)\n\n      default operator ?\n\n    AppService as abstract\n      run() as abstract\n        -> input as String\n        <- output as String?\n\n      default operator ?\n\n    MainService is AppService\n      store as DataStore!\n      logger as Logger!\n\n      override run()\n        -> input as String\n        <- output <- String()\n        saved <- store.save(input)\n        output: logger.log(\"Service processed: \" + saved)\n\n      default operator ?\n\n  defines application\n\n    LifecycleApp\n      // _prepare() creates in this order:\n      //   1. AppLogger (no dependencies)\n      //   2. InMemoryStore (depends on Logger)\n      //   3. MainService (depends on DataStore and Logger)\n      register AppLogger() as Logger\n      register InMemoryStore() as DataStore\n      register MainService() as AppService\n      // _decommission() releases in reverse:\n      //   1. MainService\n      //   2. InMemoryStore\n      //   3. AppLogger\n\n  defines program\n\n    LifecycleDemo() with application of LifecycleApp\n      stdout <- Stdout()\n\n      // === EXECUTE PHASE: all components fully initialized ===\n\n      service as AppService!\n\n      result <- service.run(\"test data\")\n      stdout.println(result)\n\n      stdout.println(\"Components created in order, released in reverse\")","migrationContext":"Java Spring: singleton (default), prototype, request, session, application scopes. Complex lifecycle: @PostConstruct, @PreDestroy, InitializingBean, DisposableBean, BeanPostProcessor. Guice: unscoped (default), @Singleton, @RequestScoped, custom scopes. .NET: transient, scoped, singleton lifetimes. Python: manual lifecycle management. Go: manual lifecycle. Rust: ownership-based lifecycle. EK9: singleton only, _prepare() creates in order, _decommission() releases in reverse, no scope mismatch bugs.","keywords":["component","create","decommission","dependency","destroy","inject","lifecycle","migrate","order","phase","prepare","reverse","scope","singleton"],"primaryTopics":[],"typicalErrors":[{"error":"E08200","correct":"register AppLogger() as Logger\n      register InMemoryStore() as DataStore\n      register MainService() as AppService","incorrect":"register MainService() as AppService\n      register AppLogger() as Logger\n      register InMemoryStore() as DataStore","explanation":"MainService depends on both DataStore and Logger, so both must be registered before MainService. The prepare phase creates components in registration order, so dependencies must come first. See ek9 -h E08200 for details."},{"error":"E08150","correct":"store as DataStore!","incorrect":"store as InMemoryStore!","explanation":"Injection fields must use abstract component types. The lifecycle manager resolves abstract types to their registered concrete implementations during the prepare phase. See ek9 -h E08150 for details."}],"companions":[]}
{"id":235,"category":"Streams and Pipelines","question":"What stream operations are available in EK9?","url":"https://ek9.io/qa/QA0235.html","alternatePhrasings":["What is the complete list of EK9 stream pipeline operations?","What can I do in a stream pipeline in EK9?","What are all the stream stages available in EK9?","How do I reference all EK9 pipeline operations?"],"answer":"EK9 stream pipelines use cat source | operations | terminal syntax. Operations fall into categories: filtering (filter, reject), transforming (map), ordering (sort), limiting (head, tail, skip), grouping (group by), flattening (flatten), deduplication (uniq), side effects (tee), and parallel execution (async). Terminals are collect as Type or > stdout.\n\nEach operation expects a specific function type: filter uses Predicate (T to Boolean), sort uses Comparator (T, T to Integer), map uses a transform function (T to R). Use 'ek9 -h stream' for the complete reference.\n\nPipeline stages chain with |:\n  cat items | filter by pred | sort | head 5 | collect as List of T\n\nSee Q120 for sort. See Q122 for collect. See Q123 for flatten. See Q124 for group by. See Q125 for head/tail/skip. See Q133 for tee and uniq. See Q237 for streams vs loops. See Q954 for collect as custom type.","ek9Example":"defines module qa.streams.reference\n\n  defines function\n\n    isPositive() as pure\n      -> num as Integer\n      <- rtn as Boolean: num > 0\n\n    doubleIt() as pure\n      -> num as Integer\n      <- rtn as Integer: num * 2\n\n    intToString() as pure\n      -> num as Integer\n      <- rtn as String: $num\n\n  defines program\n\n    StreamOpsReferenceDemo()\n      stdout <- Stdout()\n\n      numbers <- [5, -3, 8, -1, 4, 8, 2, 5, -3, 10]\n\n      // === FILTER BY ===\n      positives <- cat numbers | filter by isPositive | collect as List of Integer\n      stdout.println(`filter by: ${positives}`)\n\n      // === MAP WITH ===\n      doubled <- cat numbers | map with doubleIt | collect as List of Integer\n      stdout.println(`map with: ${doubled}`)\n\n      // === SORT ===\n      sorted <- cat numbers | sort | collect as List of Integer\n      stdout.println(`sort: ${sorted}`)\n\n      // === HEAD ===\n      firstThree <- cat numbers | head 3 | collect as List of Integer\n      stdout.println(`head 3: ${firstThree}`)\n\n      // === SKIP ===\n      afterFive <- cat numbers | skip 5 | collect as List of Integer\n      stdout.println(`skip 5: ${afterFive}`)\n\n      // === TAIL ===\n      lastTwo <- cat numbers | tail 2 | collect as List of Integer\n      stdout.println(`tail 2: ${lastTwo}`)\n\n      // === UNIQ (sort first) ===\n      unique <- cat numbers | sort | uniq | collect as List of Integer\n      stdout.println(`sort + uniq: ${unique}`)\n\n      // === COMBINED: filter + sort + head ===\n      topThreePositive <- cat numbers | filter by isPositive | sort | head 3 | collect as List of Integer\n      stdout.println(`filter+sort+head: ${topThreePositive}`)\n\n      // === COLLECT AS INTEGER (sum) ===\n      total <- cat numbers | filter by isPositive | collect as Integer\n      stdout.println(`sum of positives: ${total}`)\n\n      // === TEE for intermediate capture ===\n      captured <- List() of Integer\n      result <- cat numbers | filter by isPositive | tee in captured | sort | collect as List of Integer\n      stdout.println(`tee captured: ${captured}`)\n      stdout.println(`final sorted: ${result}`)\n\n      // === DIRECT OUTPUT ===\n      cat numbers | filter by isPositive | map with intToString > stdout","migrationContext":"Java: Stream API (filter, map, sorted, collect). Python: list comprehensions, map(), filter(). Rust: Iterator trait (filter, map, take, collect). Go: manual loops. EK9: cat | filter | sort | head | collect, Unix pipe syntax with typed function stages.","keywords":["all","async","cat","collect","complete","filter","flatten","group","head","list","map","migrate","operations","pipe","pipeline","reference","reject","skip","sort","stream","swift","tail","tee","uniq"],"primaryTopics":["stream operations","stream API","pipe operations"],"typicalErrors":[{"error":"E50030","correct":"<- rtn as Boolean: num > 0","incorrect":"<- rtn as Integer: num > 0","explanation":"Stream filter predicates must return Boolean. A function used with 'filter by' that returns Integer instead of Boolean triggers E50030. See ek9 -h E50030 for details."}],"companions":[]}
{"id":236,"category":"Streams and Pipelines","question":"How do I create a custom iterator so my type works with cat and for-in?","url":"https://ek9.io/qa/QA0236.html","alternatePhrasings":["How do I make my own type iterable in EK9?","How do I implement the iterator pattern for a custom class in EK9?","How do I stream my own type with cat in EK9?"],"answer":"To make a custom type work with cat (stream source) and for-in loops, your type needs to provide an iterator() method. EK9 uses the iterator pattern: your type returns an Iterator of T, which the stream or loop consumes.\n\nTHE ITERATOR CONTRACT\nAn iterator must support two operations:\n  hasNext()      Returns true if more elements are available.\n  next()         Returns the next element and advances.\n\nThe for-in loop calls hasNext() before each iteration and next() to get each element. The cat operation does the same to feed elements into a pipeline.\n\nPATTERN 1: DELEGATE TO A COLLECTION\nThe simplest approach is to store items in a List and delegate iteration:\n  Library\n    books as List of Book\n    iterator() as pure\n      <- rtn as Iterator of Book: books.iterator()\n\nThis is the most common pattern. Any class that wraps a collection can expose it for iteration.\n\nPATTERN 2: DYNAMIC CLASS ITERATOR\nFor lazy generation (values computed on demand), create a dynamic class that implements Iterator of T:\n  NumberRange\n    iterator()\n      <- rtn as Iterator of Integer: createIterator(start, limit)\n\n  createIterator()\n    -> start as Integer, limit as Integer\n    <- rtn as Iterator of Integer?\n    current <- Integer(start)\n    rtn: (current, limit) is Iterator of Integer as class\n      override hasNext() as pure\n        <- rtn as Boolean: current? and limit? and current <= limit\n      override next()\n        <- rtn as Integer: Integer(current)\n        current++\n      default operator ?\n\nThe dynamic class captures 'current' and 'limit' by value. Each call to next() advances the internal position. The hasNext() method checks if more elements remain.\n\nUSING WITH FOR-IN\nOnce your type has iterator(), for-in works automatically:\n  range <- NumberRange(1, 5)\n  for num in range\n    stdout.println($num)\n\nUSING WITH CAT (STREAM SOURCE)\nThe same type works as a stream source:\n  range <- NumberRange(1, 10)\n  evens <- cat range | filter by isEven | collect as List of Integer\n\nThis is the bridge between OOP types and functional stream processing. Any type with iterator() becomes a first-class stream source.\n\nFRESH ITERATORS\nThe iterator() method should create a fresh iterator each time. This allows multiple independent iterations over the same source.\n\nBUILT-IN ITERABLE TYPES\nThese types already provide iterators: List, Dict (iterates DictEntry values), Optional (yields 0 or 1 elements), Result (yields ok value if present), and all enumeration types.\n\nSee Q65 for for-in loops. See Q85 for Optional as iterator. See Q87 for Result as iterator. See Q89 for stream pipeline basics. See Q99 for enumeration iteration. See Q115 for dynamic classes. See Q224 for enum streams. See Q235 for complete stream operations reference. See Q237 for streams vs loops decision guide.","ek9Example":"defines module qa.streams.customiterators\n\n  defines class\n\n    NumberRange\n      start as Integer?\n      limit as Integer?\n\n      default private NumberRange() as pure\n\n      NumberRange() as pure\n        ->\n          start as Integer\n          limit as Integer\n        this.start :=? start\n        this.limit :=? limit\n\n      iterator()\n        <- rtn as Iterator of Integer: createIterator(start, limit)\n\n      default operator ?\n\n  defines function\n\n    createIterator()\n      ->\n        start as Integer\n        limit as Integer\n      <- rtn as Iterator of Integer?\n\n      current <- Integer(start)\n      rtn: (current, limit) is Iterator of Integer as class\n        override hasNext() as pure\n          <- rtn as Boolean: current? and limit? and current <= limit\n\n        override next()\n          <- rtn as Integer: Integer(current)\n          current++\n\n        default operator ?\n\n    isEven() as pure\n      -> num as Integer\n      <- rtn as Boolean: num mod 2 == 0\n\n  defines program\n\n    CustomIteratorDemo()\n      stdout <- Stdout()\n\n      // === FOR-IN WITH CUSTOM ITERATOR ===\n\n      range <- NumberRange(1, 5)\n      stdout.println(\"For-in iteration:\")\n      for num in range\n        stdout.println(`  ${num}`)\n\n      // === CAT WITH CUSTOM ITERATOR (stream source) ===\n\n      range2 <- NumberRange(1, 10)\n      evens <- cat range2 | filter by isEven | collect as List of Integer\n      stdout.println(`Even numbers 1-10: ${evens}`)\n\n      // === MULTIPLE ITERATIONS (fresh iterator each time) ===\n\n      range3 <- NumberRange(1, 3)\n      first <- cat range3 | collect as List of Integer\n      second <- cat range3 | collect as List of Integer\n      stdout.println(`First pass: ${first}`)\n      stdout.println(`Second pass: ${second}`)","migrationContext":"Java: implement Iterable<T> with iterator() method, Iterator<T> with hasNext() and next(), for-each loop and Stream.of() consume iterables. Python: implement __iter__() returning self with __next__(), raise StopIteration when done, for-in and list comprehensions consume iterables. Rust: implement Iterator trait with next() returning Option<T>, for-in and .iter() consume iterators, IntoIterator for owned iteration. Go: no iterator interface, manual for-range with index, channels for generator pattern. Kotlin: implement Iterable<T> or Iterator<T>, sequence builders for lazy generation. JavaScript: implement Symbol.iterator returning object with next() returning value and done. EK9: provide iterator() method returning Iterator of T, use dynamic class is Iterator of T as class with override hasNext() and override next(), works with both for-in loops and cat stream pipelines.","keywords":["anonymous","capture","cat","class","closure","custom","define","dynamic","for-in","hasNext","implement","iterable","iterator","loop","next","pattern","pipe","pipeline","source","stream","type"],"primaryTopics":["custom iterator","iterator","iterable"],"typicalErrors":[{"error":"E50060","correct":"stdout.println(`Even numbers 1-10: ${evens}`)","incorrect":"stdout.display(`Even numbers 1-10: ${evens}`)","explanation":"Stdout does not have a display() method. The correct method is println(). Calling a non-existent method triggers E50060 — method not resolved. See ek9 -h E50060 for details."}],"companions":[]}
{"id":237,"category":"Streams and Pipelines","question":"When should I use a stream pipeline vs a loop in EK9?","url":"https://ek9.io/qa/QA0237.html","alternatePhrasings":["What is the difference between a stream and a loop in EK9?","Should I use cat pipe collect or a for loop in EK9?","When are streams better than loops in EK9?","How do I choose between a stream pipeline and a for-in loop in EK9?"],"answer":"EK9 provides both stream pipelines and loops. Use streams for filtering, transforming, limiting, and chaining operations. Use loops for side effects and condition-based iteration.\n\nUSE STREAMS WHEN\nFiltering (filter by), transforming (map with), limiting (head N as early exit), deduplication (sort | uniq), chaining multiple operations, or grouping (group by).\n\nUSE FOR-IN LOOPS WHEN\nSide effects on every item (printing, saving). Simple accumulation where a loop is more readable.\n\nUSE FOR-IN EXPRESSIONS WHEN\nComplex multi-variable accumulation or fold/reduce logic. See Q82.\n\nUSE WHILE LOOPS WHEN\nCondition-based iteration (polling, retrying, external APIs). See Q66.\n\nDECISION SUMMARY\n  Filter or limit?           Stream (filter by, head)\n  Transform each item?       Stream (map with)\n  Early exit?                Stream (head N) — See Q125\n  Side effects on all items? For-in loop\n  Complex accumulation?      For-in expression or collect as\n  Condition-based?           While loop\n\nSee Q51 for abstract functions in pipelines. See Q52 for dynamic functions. See Q54 for Predicate/Comparator types. See Q64-Q66 for loop types. See Q80/Q82 for loop expressions. See Q89 for stream basics. See Q122 for collect as. See Q125 for head as early exit. See Q145 for replacing break/continue. See Q235 for stream operations reference. See Q261 for idiomatic patterns.","ek9Example":"defines module qa.streams.vsloops\n\n  defines function\n\n    isEven() as pure\n      -> num as Integer\n      <- rtn as Boolean: num mod 2 == 0\n\n    doubleIt() as pure\n      -> num as Integer\n      <- rtn as Integer: num * 2\n\n    intToString() as pure\n      -> num as Integer\n      <- rtn as String: $num\n\n  defines program\n\n    StreamsVsLoopsDemo()\n      stdout <- Stdout()\n\n      numbers <- [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]\n\n      // === STREAM: Filter + Transform + Limit ===\n\n      topThreeDoubledEvens <- cat numbers\n        | filter by isEven\n        | map with doubleIt\n        | head 3\n        | collect as List of Integer\n      stdout.println(`Stream (filter+map+head): ${topThreeDoubledEvens}`)\n\n      // === STREAM: Sum with collect as Integer ===\n\n      total <- cat numbers | collect as Integer\n      stdout.println(`Stream sum: ${total}`)\n\n      // === FOR-IN LOOP: Side effects ===\n\n      stdout.println(\"Loop (side effects):\")\n      for num in numbers\n        stdout.println(`  Processing ${num}`)\n\n      // === FOR-IN EXPRESSION: Accumulation ===\n\n      sum <- for n in numbers\n        <- rtn <- 0\n        rtn: rtn + n\n      stdout.println(`Loop expression sum: ${sum}`)\n\n      // === FOR-IN EXPRESSION: Complex accumulation ===\n\n      evenCount <- for n in numbers\n        <- rtn <- 0\n        if n mod 2 == 0\n          rtn: rtn + 1\n      stdout.println(`Even count via loop: ${evenCount}`)\n\n      // === STREAM: Same result as above ===\n\n      evenList <- cat numbers | filter by isEven | collect as List of Integer\n      stdout.println(`Even count via stream: ${length evenList}`)","migrationContext":"Java/Kotlin: Streams vs for-each, similar trade-offs but EK9 head replaces break. Python: list comprehensions vs for-loops. Go: only for-loops. EK9: cat|filter|map|collect for chains, for-in for side effects, for-in expression for accumulation, head replaces break.","keywords":["accumulate","choose","collect","comparison","decide","effect","filter","for","guide","head","loop","pipe","side","stream","transform","versus","vs","when","while"],"primaryTopics":[],"typicalErrors":[{"error":"E07520","correct":"<- rtn as Boolean: num mod 2 == 0","incorrect":"<- rtn as Integer: num mod 2","explanation":"A predicate function used with 'filter by' must return Boolean. Returning Integer (e.g., the modulo result) instead of Boolean triggers E07520. See ek9 -h E07520 for details."}],"companions":[]}
{"id":238,"category":"Operators and Expressions","question":"What operators does EK9 support and why is the set fixed?","url":"https://ek9.io/qa/QA0238.html","alternatePhrasings":["Can I define new operator symbols in EK9?","What is the complete list of EK9 operators?","How does EK9 enforce operator rules at compile time?"],"answer":"EK9 supports approximately 50 operators in a FIXED set. Unlike C++ where you can define arbitrary operator symbols, EK9 only allows you to IMPLEMENT existing operators on your types. You cannot invent new operator symbols. This is a deliberate design choice that keeps the language predictable and enables comprehensive compile-time validation.\n\nFIXED SET PHILOSOPHY\nEvery operator in EK9 has defined semantics: purity requirements, parameter counts, and return types. The compiler enforces ALL of these. If you get any wrong, you get a specific error code telling you exactly what is incorrect.\n\nPURITY ENFORCEMENT\nSome operators MUST be pure (no side effects): ==, <>, <=>, <, >, <=, >=, $, #?, ?, +, -, *, /, ~, !, abs, sqrt, mod, rem, and, or, xor, contains, matches, #^, #<, #>, length, empty, close.\nSome operators CANNOT be pure: +=, -=, *=, /=, ++, --, :=:, :~:, :^:, |.\nViolating purity rules produces errors E07500 (must be pure) or E07510 (cannot be pure).\n\nPARAMETER COUNT ENFORCEMENT\nEach operator has a fixed parameter count. Binary operators (==, +, -, *, /, and, or) take exactly 1 argument. Unary operators (~, !, ?, $, #?, abs, sqrt, ++, --) take 0 arguments. Incorrect counts produce E06280 (too many parameters) or E06290 (too few parameters).\n\nRETURN TYPE ENFORCEMENT\nReturn types are strictly enforced: ? must return Boolean, #? must return Integer, $ must return String, and $$ must return JSON. Comparison operators (==, <>, <, >, <=, >=) must return Boolean. The <=> operator must return Integer. Mutation operators (+=, -=, ++, --, :=:, :~:, :^:) must NOT return anything. Violations produce E07520, E07550, E07570, E07580, E07410, E07420, or E07430.\n\nOPERATOR CATEGORIES\nComparison: ==, <>, <, >, <=, >=, <=>, <~> (pure, 1 arg, return Boolean or Integer)\nArithmetic: +, -, *, /, ^, mod, rem (pure, 1 arg, return value)\nUnary: - (negate), ~ (NOT for Boolean/Bits), !, abs, sqrt (pure, 0 args, return value)\nMutation: +=, -=, *=, /=, ++, --, :=:, :~:, :^:, | (not pure, no return)\nConversion: $, $$, #?, #^, #<, #> (pure, 0 args, return value)\nState: ?, empty, length, close (pure, 0 args)\nLogical: and, or, xor (pure, 1 arg, return Boolean)\nBit shift: <<, >> (pure, 1 arg, return value)\nCoalescing: ??, ?:, <?, <=?, >?, >=? (expression-level, not implementable)\nQuery: contains, matches (pure, 1 arg, return Boolean)\n\nSee Q96 for class operator syntax. See Q116 for default operator generation. See Q239 for comparison operators. See Q240 for arithmetic operators. See Q241 for mutation operators. See Q242 for conversion operators. See Q243 for coalescing operators. See Q244 for boolean and bitwise operators. See Q245 for implementing a complete custom type. See Q248 for error code ranges and lookup. See Q254 for the Any type and its default operators inherited by all types. See Q279 for common AI operator confusion.","ek9Example":"defines module qa.operators.finiteset\n\n  defines class\n\n    Score\n      points <- Integer()\n\n      Score() as pure\n        -> points as Integer\n        this.points :=: points\n\n      operator == as pure\n        -> other as Score\n        <- rtn as Boolean: points == other.points\n\n      operator <> as pure\n        -> other as Score\n        <- rtn as Boolean: points <> other.points\n\n      operator <=> as pure\n        -> other as Score\n        <- rtn as Integer: points <=> other.points\n\n      operator + as pure\n        -> other as Score\n        <- rtn as Score: Score(points + other.points)\n\n      operator $ as pure\n        <- rtn as String: $points\n\n      operator #? as pure\n        <- rtn as Integer: #?points\n\n      override operator ? as pure\n        <- rtn as Boolean: points?\n\n  defines program\n\n    FiniteOperatorSetDemo()\n      stdout <- Stdout()\n\n      a <- Score(10)\n      b <- Score(20)\n\n      // Comparison operators\n      stdout.println(`Equal: ${a == b}`)\n      stdout.println(`Compare: ${a <=> b}`)\n\n      // Arithmetic operator\n      total <- a + b\n      stdout.println(`Total: ${total}`)\n\n      // Conversion operators\n      stdout.println(`String: ${a}`)","migrationContext":"C++: arbitrary operator overloading including new symbols via operator keyword, no purity or return type enforcement, error-prone. Java: no operator overloading at all, everything is methods. Python: magic methods (__add__, __eq__) with no compiler enforcement of signatures. Rust: trait-based (Add, Eq, Ord) with strict signatures but verbose. Go: no operator overloading. Kotlin: operator keyword with some constraints but less strict than EK9. JavaScript: no custom operators, Symbol-based limited overloading. EK9: fixed set of ~50 operators, compiler enforces purity, parameter count, and return type for every operator.","keywords":["compile","enforce","error","expression","fixed","immutable","migrate","mutation","operator","overload","parameter","pure","purity","return","set","side-effect","type"],"primaryTopics":["operators","operator list","operator reference"],"typicalErrors":[{"error":"E07500","correct":"operator == as pure","incorrect":"operator ==","explanation":"Equality operators must be marked 'as pure' to guarantee no side effects. Omitting 'as pure' on ==, <>, or <=> triggers E07500. See ek9 -h E07500 for details."},{"error":"E07520","correct":"<- rtn as Boolean: points == other.points","incorrect":"<- rtn as String: points == other.points","explanation":"The == operator must return Boolean. Returning a non-Boolean type like String triggers E07520. See ek9 -h E07520 for details."},{"error":"E07550","correct":"<- rtn as Integer: points <=> other.points","incorrect":"<- rtn as Boolean: points <=> other.points","explanation":"The <=> operator must return Integer for three-way ordering. Returning Boolean instead triggers E07550. See ek9 -h E07550 for details."},{"error":"E50060","correct":"stdout.println(`Equal: ${a == b}`)","incorrect":"stdout.println(`Equal: ${a.equals(b)}`)","explanation":"EK9 types do not have an equals() method. Use the == operator for equality comparisons. Calling a non-existent method triggers E50060 — method not resolved. See ek9 -h E50060 for details."}],"companions":[]}
{"id":239,"category":"Operators and Expressions","question":"How do comparison and ordering operators work in EK9?","url":"https://ek9.io/qa/QA0239.html","alternatePhrasings":["How do I compare two objects in EK9?","What is the spaceship operator in EK9?","How does fuzzy matching work with the <~> operator?"],"answer":"EK9 provides a comprehensive set of comparison and ordering operators. All comparison operators MUST be pure, take exactly 1 argument, and return specific types. If either operand is unset, the result is unset.\n\nEQUALITY AND INEQUALITY\nThe == operator tests equality and returns Boolean. The <> operator tests inequality and returns Boolean. Both take one argument of the same type.\n  operator == as pure\n    -> other as MyType\n    <- rtn as Boolean: ...\n\nORDERING\nThe <, >, <=, >= operators test ordering and return Boolean. They take one argument.\n  if score1 > score2\n    stdout.println(\"Higher\")\n\nTHREE-WAY COMPARISON (SPACESHIP)\nThe <=> operator returns Integer: negative if less, zero if equal, positive if greater. This is the foundation for sorting.\n  operator <=> as pure\n    -> other as MyType\n    <- rtn as Integer: ...\nWhen you implement <=>, you typically derive <, >, <=, >= from it.\n\nFUZZY COMPARISON\nThe <~> operator returns Integer representing a distance or similarity score. For String, it returns the Levenshtein edit distance. The lower the value, the more similar.\n  distance <- \"hello\" <~> \"hallo\"\n  stdout.println(`Edit distance: ${distance}`)\n\nCONTAINS AND MATCHES\nThe 'contains' operator checks if one value contains another. The 'matches' operator checks pattern matching (e.g., regex). Both are pure, take 1 argument, and return Boolean.\n  if greeting contains \"hello\"\n    stdout.println(\"Found\")\n  if email matches /[a-z]+@[a-z]+\\.[a-z]+/\n    stdout.println(\"Valid\")\n\nUNSET PROPAGATION\nIf either operand is unset, comparison results are unset (not false). This prevents silent logic errors with missing data.\n\nSee Q96 for class operators. See Q238 for the complete operator set. See Q243 for coalescing operators that handle unset values.","ek9Example":"defines module qa.operators.comparison\n\n  defines class\n\n    Temperature\n      celsius <- Float()\n\n      Temperature() as pure\n        -> celsius as Float\n        this.celsius :=: celsius\n\n      operator == as pure\n        -> other as Temperature\n        <- rtn as Boolean: celsius == other.celsius\n\n      operator <> as pure\n        -> other as Temperature\n        <- rtn as Boolean: celsius <> other.celsius\n\n      operator <=> as pure\n        -> other as Temperature\n        <- rtn as Integer: celsius <=> other.celsius\n\n      operator < as pure\n        -> other as Temperature\n        <- rtn as Boolean: celsius < other.celsius\n\n      operator > as pure\n        -> other as Temperature\n        <- rtn as Boolean: celsius > other.celsius\n\n      operator <= as pure\n        -> other as Temperature\n        <- rtn as Boolean: celsius <= other.celsius\n\n      operator >= as pure\n        -> other as Temperature\n        <- rtn as Boolean: celsius >= other.celsius\n\n      operator $ as pure\n        <- rtn as String: `${celsius}C`\n\n      override operator ? as pure\n        <- rtn as Boolean: celsius?\n\n  defines program\n\n    ComparisonDemo()\n      stdout <- Stdout()\n\n      freezing <- Temperature(0.0)\n      boiling <- Temperature(100.0)\n      body <- Temperature(37.0)\n\n      // === EQUALITY AND INEQUALITY ===\n\n      stdout.println(`Equal: ${freezing == boiling}`)\n      stdout.println(`Not equal: ${freezing <> boiling}`)\n\n      // === ORDERING ===\n\n      stdout.println(`Freezing < boiling: ${freezing < boiling}`)\n      stdout.println(`Body >= freezing: ${body >= freezing}`)\n\n      // === THREE-WAY COMPARISON ===\n\n      cmp <- freezing <=> boiling\n      stdout.println(`Compare: ${cmp}`)\n\n      // === FUZZY COMPARISON ON STRINGS ===\n\n      reference <- \"hello\"\n      distance <- reference <~> \"hallo\"\n      stdout.println(`Fuzzy distance: ${distance}`)\n\n      // === CONTAINS AND MATCHES ===\n\n      greeting <- \"hello world\"\n      stdout.println(`Contains hello: ${greeting contains \"hello\"}`)","migrationContext":"Java: equals() for equality, compareTo() for ordering (returns int), no fuzzy match built-in. Python: __eq__, __lt__, __gt__ etc., functools.total_ordering derives from __lt__ and __eq__. Rust: PartialEq, Eq, PartialOrd, Ord traits, derived with #[derive()]. Go: no operator overloading, manual comparison functions. Kotlin: compareTo() operator, == delegates to equals(). JavaScript: no custom comparisons, == with type coercion problems. EK9: ==, <>, <, >, <=, >= return Boolean, <=> returns Integer, <~> returns fuzzy distance, all must be pure with 1 argument.","keywords":["Boolean","Integer","comparison","contains","equal","expression","fuzzy","greater","less","matches","operator","ordering","sort","spaceship","unset"],"primaryTopics":["comparison operator","ordering","equals"],"typicalErrors":[{"error":"E07500","correct":"operator <=> as pure","incorrect":"operator <=>","explanation":"The spaceship comparison operator must be marked 'as pure' because comparisons cannot have side effects. Omitting 'as pure' triggers E07500. See ek9 -h E07500 for details."},{"error":"E07550","correct":"<- rtn as Integer: celsius <=> other.celsius","incorrect":"<- rtn as String: celsius <=> other.celsius","explanation":"The <=> operator must return Integer for three-way comparison ordering. Returning String instead triggers E07550. See ek9 -h E07550 for details."},{"error":"E07520","correct":"<- rtn as Boolean: celsius < other.celsius","incorrect":"<- rtn as Integer: celsius < other.celsius","explanation":"The < operator must return Boolean (true or false). Returning Integer instead triggers E07520. See ek9 -h E07520 for details."}],"companions":[]}
{"id":240,"category":"Operators and Expressions","question":"How do arithmetic and mathematical operators work in EK9?","url":"https://ek9.io/qa/QA0240.html","alternatePhrasings":["What is the difference between mod and rem in EK9?","How do I use sqrt and abs in EK9?","What mathematical operators does EK9 provide?"],"answer":"EK9 provides binary arithmetic operators, unary mathematical operators, and specialized math operations. All arithmetic and math operators MUST be pure.\n\nBINARY ARITHMETIC\nThe +, -, *, / operators are pure, take 1 argument, and return a new value of the same or compatible type. They do NOT modify the original.\n  result <- a + b\n  quotient <- total / count\nThe ^ operator computes power: base ^ exponent.\n  squared <- value ^ 2\n\nUNARY OPERATORS\nThe - operator as unary (0 arguments) negates a numeric value:\n  negative <- -positive\nFor Integer and Float, unary - returns the negated value. The ~ operator is NOT for numeric negation. It is logical NOT for Boolean and bitwise NOT for Bits.\n\nThe ! operator computes factorial (returns result, 0 arguments):\n  result <- 5!\nFactorial is defined on Integer.\n\nSQRT AND ABS\nThe sqrt operator returns the square root. The abs operator returns the absolute value. Both are pure, take 0 arguments, and return the same type.\n  root <- sqrt value\n  magnitude <- abs negativeNumber\n\nMOD VS REM\nBoth mod and rem are pure, take 1 argument, and return a result. The critical difference:\n  mod (modulus): Result ALWAYS has the same sign as the divisor (mathematical modulus).\n  rem (remainder): Result has the same sign as the dividend (truncated division remainder).\n\nWith positive numbers they are identical:\n  7 mod 3   yields 1\n  7 rem 3   yields 1\n\nWith negative numbers they differ:\n  -7 mod 3  yields 2  (always positive when divisor is positive)\n  -7 rem 3  yields -1 (preserves dividend sign)\n\nUse mod for cyclic operations (array indexing, clock arithmetic). Use rem for mathematical remainder after division.\n\nSee Q238 for the complete operator set. See Q241 for mutation operators (+=, -=). See Q39 for integer and float basics.","ek9Example":"defines module qa.operators.arithmetic\n\n  defines program\n\n    ArithmeticDemo()\n      stdout <- Stdout()\n\n      // === BINARY ARITHMETIC ===\n\n      a <- 10\n      b <- 3\n      stdout.println(`Add: ${a + b}`)\n      stdout.println(`Subtract: ${a - b}`)\n      stdout.println(`Multiply: ${a * b}`)\n      stdout.println(`Divide: ${a / b}`)\n\n      // === POWER ===\n\n      base <- 2\n      squared <- base ^ 3\n      stdout.println(`Power: ${squared}`)\n\n      // === MOD VS REM ===\n\n      stdout.println(`7 mod 3: ${7 mod 3}`)\n      stdout.println(`7 rem 3: ${7 rem 3}`)\n\n      // With negative numbers - the critical difference\n      stdout.println(`-7 mod 3: ${-7 mod 3}`)\n      stdout.println(`-7 rem 3: ${-7 rem 3}`)\n\n      // === UNARY NEGATE (unary minus) ===\n\n      positive <- 42\n      negative <- -positive\n      stdout.println(`Negate: ${negative}`)\n\n      // === ABS AND SQRT ===\n\n      negNum <- -25\n      stdout.println(`Abs: ${abs negNum}`)\n\n      floatVal <- 16.0\n      stdout.println(`Sqrt: ${sqrt floatVal}`)","migrationContext":"Java: % is remainder (not modulus), Math.floorMod() for true modulus, Math.abs(), Math.sqrt(). Python: % is true modulus (matches divisor sign), divmod() for both. Rust: % is remainder, rem_euclid() for modulus. Go: % is remainder, no built-in modulus. Kotlin: % is remainder, mod() for modulus. JavaScript: % is remainder, no built-in modulus. EK9: separate mod (true modulus) and rem (remainder) operators, plus sqrt, abs, ! (factorial), unary - (negate), ^ (power).","keywords":["abs","add","arithmetic","divide","expression","factorial","math","mod","modulus","multiply","negate","operator","power","rem","remainder","sqrt","subtract"],"primaryTopics":["arithmetic","math operator","add subtract multiply"],"typicalErrors":[{"error":"E50060","correct":"abs negNum","incorrect":"negNum.absoluteValue()","explanation":"EK9 Integer has no absoluteValue() method. Use the prefix operator 'abs value'. While value.abs() also works, Java-style verbose names like absoluteValue() do not exist. See ek9 -h E50060 for details."},{"error":"E50060","correct":"sqrt floatVal","incorrect":"floatVal.squareRoot()","explanation":"EK9 Float has no squareRoot() method. Use the prefix operator 'sqrt value'. While value.sqrt() also works, Java-style verbose names like squareRoot() do not exist. See ek9 -h E50060 for details."}],"companions":[]}
{"id":241,"category":"Operators and Expressions","question":"What are mutation operators and how do they differ from pure operators?","url":"https://ek9.io/qa/QA0241.html","alternatePhrasings":["What is the difference between + and += in EK9?","How do increment and decrement work in EK9?","Which EK9 operators modify the object in place?"],"answer":"Mutation operators modify an object in place. They CANNOT be marked pure and MUST NOT return a value. This is the opposite of arithmetic operators like + which create NEW values and MUST be pure.\n\nCOMPOUND ASSIGNMENT\nThe +=, -=, *=, /= operators modify the object in place. They take 1 argument and return nothing.\n  score += 10\n  balance -= withdrawal\n  count *= factor\nThese are equivalent to reassigning: score: score + 10, but more concise.\n\nINCREMENT AND DECREMENT\nThe ++ and -- operators take 0 arguments and return nothing.\n  counter++\n  remaining--\n\nCOPY, MERGE, REPLACE\nThe :=: (copy), :~: (merge), and :^: (replace) operators are mutation operators. They take 1 argument and return nothing.\n  target :=: source\n  config :~: partial\n  record :^: replacement\nCopy overwrites all fields. Merge copies only SET fields. Replace is a full content replacement.\n\nPIPE OPERATOR\nThe | (pipe) operator is a mutation operator used in stream processing. It takes 1 argument and returns nothing.\n\nPURE VS MUTATION: THE KEY DISTINCTION\nPure operator + creates a NEW value:\n  c <- a + b\nThe original 'a' is unchanged. A new value is created and assigned to 'c'.\n\nMutation operator += modifies IN PLACE:\n  a += b\nThe object 'a' is directly modified. No new value is created.\n\nIMPLEMENTING MUTATION OPERATORS\nMutation operators CANNOT have 'as pure' and MUST NOT have a return declaration:\n  operator +=\n    -> other as MyType\n    this.value: this.value + other.value\n\n  operator ++\n    this.value: this.value + 1\n\nSee Q98 for record copy, merge, replace. See Q240 for pure arithmetic operators. See Q238 for the complete operator set. See Q273 for how purity prevents mutation-based security attacks.","ek9Example":"defines module qa.operators.mutation\n\n  defines class\n\n    Counter\n      count <- Integer()\n\n      Counter() as pure\n        -> initial as Integer\n        this.count :=: initial\n\n      // Pure: creates NEW value\n      operator + as pure\n        -> other as Counter\n        <- rtn as Counter: Counter(count + other.count)\n\n      // Mutation: modifies IN PLACE\n      operator +=\n        -> other as Counter\n        count += other.count\n\n      operator ++\n        count++\n\n      operator --\n        count--\n\n      operator :=:\n        -> source as Counter\n        count :=: source.count\n\n      operator $ as pure\n        <- rtn as String: $count\n\n      override operator ? as pure\n        <- rtn as Boolean: count?\n\n  defines program\n\n    MutationDemo()\n      stdout <- Stdout()\n\n      // === PURE + CREATES NEW VALUE ===\n\n      a <- Counter(10)\n      b <- Counter(5)\n      c <- a + b\n      stdout.println(`a after +: ${a}`)\n      stdout.println(`c (new value): ${c}`)\n\n      // === MUTATION += MODIFIES IN PLACE ===\n\n      a += b\n      stdout.println(`a after +=: ${a}`)\n\n      // === INCREMENT AND DECREMENT ===\n\n      counter <- Counter(0)\n      counter++\n      counter++\n      counter++\n      stdout.println(`After 3 increments: ${counter}`)\n      counter--\n      stdout.println(`After decrement: ${counter}`)\n\n      // === COPY ===\n\n      original <- Counter(42)\n      copied <- Counter(0)\n      copied :=: original\n      stdout.println(`Copied: ${copied}`)","migrationContext":"Java: += is syntactic sugar for x = x + op, ++ is increment, no copy/merge/replace operators. Python: __iadd__ for +=, no ++ operator, copy via copy.copy(). Rust: AddAssign trait for +=, no ++ operator, Clone trait for copy. Go: += is built-in, ++ is statement-only (not expression), no custom operators. Kotlin: plusAssign for +=, ++ via inc(), no merge/replace. JavaScript: += and ++ are built-in, no custom operator definitions. EK9: +=, -=, *=, /= (compound assignment), ++, -- (increment/decrement), :=: (copy), :~: (merge), :^: (replace), | (pipe), all CANNOT be pure, MUST NOT return value.","keywords":["assignment","compound","copy","decrement","effect","expression","immutable","increment","merge","modify","mutate","mutation","operator","pipe","place","pure","replace","side","side-effect"],"primaryTopics":[],"typicalErrors":[{"error":"E07510","correct":"operator +=\n        -> other as Counter\n        count += other.count","incorrect":"operator += as pure\n        -> other as Counter\n        count += other.count","explanation":"Mutation operators (+=, -=, :=:, :~:, :^:, ++, --) modify the object in place and CANNOT be marked pure. Adding 'as pure' to a mutation operator triggers E07510. See ek9 -h E07510 for details."}],"companions":[]}
{"id":242,"category":"Operators and Expressions","question":"How do conversion and introspection operators work in EK9?","url":"https://ek9.io/qa/QA0242.html","alternatePhrasings":["How does string interpolation use the $ operator in EK9?","What do the prefix and suffix operators do in EK9?","How do I convert my type to String or JSON in EK9?"],"answer":"EK9 provides conversion and introspection operators that extract information from objects. All are pure, take 0 arguments, and return strictly enforced types.\n\nSTRING CONVERSION ($)\nThe $ operator returns a String representation. It is called implicitly in string interpolation.\n  operator $ as pure\n    <- rtn as String: ...\nWhen you write `${myObj}`, the compiler calls the $ operator.\n\nJSON CONVERSION ($$)\nThe $$ operator returns a JSON representation as a String. The JSON type has this built-in.\n  operator $$ as pure\n    <- rtn as String: ...\n\nHASHCODE (#?)\nThe #? operator returns an Integer hash code for use in Dict keys and equality checking.\n  operator #? as pure\n    <- rtn as Integer: ...\n\nISSET (?)\nThe ? operator returns Boolean indicating whether the object has a meaningful, usable value.\n  operator ? as pure\n    <- rtn as Boolean: ...\nThis is central to EK9's tri-state semantics (absent, present-unset, present-set).\n\nPROMOTE (#^)\nThe #^ operator returns a DIFFERENT type (type promotion). It MUST return a type different from self. The compiler uses this for automatic type widening.\n  operator #^ as pure\n    <- rtn as Float: ...\n\nPREFIX AND SUFFIX (#< and #>)\nThe #< operator extracts a prefix, the #> operator extracts a suffix. On String, #< returns the first character and #> returns the last character.\n  first <- #< fullName\n  last <- #> fullName\n\nEMPTY AND LENGTH\nThe 'empty' operator returns Boolean (true if the object is empty). The 'length' operator returns Integer (size/count).\n  if empty collection\n    stdout.println(\"Nothing here\")\n  size <- length myList\n\nCLOSE\nThe 'close' operator performs resource cleanup. It is pure and returns nothing.\n\nSee Q25 for the promote operator in detail. See Q96 for class operators. See Q116 for default operator generation. See Q238 for the complete operator set. See Q245 for a complete custom type example.","ek9Example":"defines module qa.operators.conversion\n\n  defines class\n\n    Money\n      amount <- Float()\n      currency <- String()\n\n      Money() as pure\n        ->\n          amount as Float\n          currency as String\n        this.amount :=: amount\n        this.currency :=: currency\n\n      operator $ as pure\n        <- rtn as String: `${currency} ${amount}`\n\n      operator #? as pure\n        <- rtn as Integer: #?amount + #?currency\n\n      override operator ? as pure\n        <- rtn as Boolean: amount? and currency?\n\n      operator #^ as pure\n        <- rtn as Float: Float(amount)\n\n      operator <=> as pure\n        -> other as Money\n        <- rtn as Integer: amount <=> other.amount\n\n      operator == as pure\n        -> other as Money\n        <- rtn as Boolean: amount == other.amount and currency == other.currency\n\n  defines program\n\n    ConversionDemo()\n      stdout <- Stdout()\n\n      price <- Money(19.99, \"USD\")\n\n      // === STRING CONVERSION $ (implicit in interpolation) ===\n\n      stdout.println(`Price: ${price}`)\n\n      // === ISSET ? ===\n\n      stdout.println(`Is set: ${price?}`)\n\n      // === HASHCODE #? ===\n\n      hash <- #? price\n      stdout.println(`Hash: ${hash}`)\n\n      // === PROMOTE #^ ===\n\n      floatValue as Float: price\n      stdout.println(`Promoted to Float: ${floatValue}`)\n\n      // === PREFIX AND SUFFIX ON STRING ===\n\n      name <- \"Hello\"\n      first <- #< name\n      last <- #> name\n      stdout.println(`Prefix: ${first}`)\n      stdout.println(`Suffix: ${last}`)\n\n      // === LENGTH ===\n\n      text <- \"Hello World\"\n      size <- length text\n      stdout.println(`Length: ${size}`)","migrationContext":"Java: toString() for String, no built-in JSON, hashCode() for hash, equals() check not isSet, no prefix/suffix operators. Python: __str__() and __repr__() for String, json.dumps() for JSON, __hash__() for hash, __bool__() for truthiness. Rust: Display trait for String, serde for JSON, Hash trait for hash, no isSet concept. Go: String() method by convention, json.Marshal() for JSON, no hash interface. Kotlin: toString() for String, no built-in JSON operator, hashCode() for hash. JavaScript: toString() for String, JSON.stringify() for JSON, no custom hash. EK9: $ returns String, $$ returns JSON, #? returns Integer hashcode, ? returns Boolean isSet, #^ returns promoted type, #< prefix, #> suffix, empty returns Boolean, length returns Integer.","keywords":["backtick","close","conversion","empty","expression","hashcode","interpolation","introspection","isset","json","length","operator","prefix","promote","string","suffix"],"primaryTopics":[],"typicalErrors":[{"error":"E50060","correct":"size <- length text","incorrect":"size <- text.size()","explanation":"String has no size() method. Use the length prefix operator or .length() method for string length. See ek9 -h E50060 for details."},{"error":"E07500","correct":"operator $ as pure","incorrect":"operator $","explanation":"Conversion operators like $, #?, and ? must be marked 'as pure' because they extract information without side effects. Omitting 'as pure' triggers E07500. See ek9 -h E07500 for details."},{"error":"E07550","correct":"<- rtn as Integer: #?amount + #?currency","incorrect":"<- rtn as String: #?amount + #?currency","explanation":"The #? (hashcode) operator must return Integer. Returning String instead triggers E07550. See ek9 -h E07550 for details."}],"companions":[]}
{"id":243,"category":"Operators and Expressions","question":"How do coalescing operators handle unset values in EK9?","url":"https://ek9.io/qa/QA0243.html","alternatePhrasings":["What is the null coalescing operator in EK9?","How does the Elvis operator work in EK9?","How do I pick the lesser or greater of two possibly unset values?"],"answer":"EK9 provides coalescing operators that safely handle unset values. These are expression-level operators (not implementable on your types) that the compiler handles directly.\n\nNULL COALESCING (??)\nThe ?? operator checks if the left operand exists in memory. If the left is invalid (absent), it returns the right.\n  result <- possiblyAbsent ?? fallback\nThis is a memory-level check, not an isSet check.\n\nELVIS OPERATOR (?:)\nThe ?: operator checks if the left is both present AND set (via isSet). If the left is absent or unset, it returns the right.\n  result <- possiblyUnset ?: fallback\nThis is more thorough than ?? because it also checks the isSet state.\n\nCOALESCING COMPARISONS\nThese operators compare two values, handling the case where one or both might be unset:\n  <? (lesser coalescing): returns the lesser value, or whichever is valid\n  <=? (lesser-or-equal coalescing): returns the lesser-or-equal value\n  >? (greater coalescing): returns the greater value, or whichever is valid\n  >=? (greater-or-equal coalescing): returns the greater-or-equal value\n\nPriority rules: If left is invalid, return right. If right is invalid, return left. If both valid, compare and return the winner.\n  minimum <- a <? b\n  maximum <- a >? b\n\nNOTE: :=? is NOT a coalescing operator. It is an ASSIGNMENT operator that assigns only if the target is unset. See Q1152 for :=? details. See Q1226 for <? vs :=? contrast.\n\nREPLACING VERBOSE IF/ELSE\nWithout coalescing:\n  if a?\n    if b?\n      if a < b\n        result: a\n      else\n        result: b\n    else\n      result: a\n  else\n    result: b\n\nWith coalescing:\n  result <- a <? b\nOne line replaces a nested if/else tree.\n\nSee Q75 for guard switch. See Q161 for Dict safe access. See Q168 for fallback values. See Q239 for comparison operators. See Q238 for the complete operator set.","ek9Example":"defines module qa.operators.coalescing\n\n  defines program\n\n    CoalescingDemo()\n      stdout <- Stdout()\n\n      // === ELVIS OPERATOR ?: ===\n\n      name <- String()\n      displayName <- name ?: \"Anonymous\"\n      stdout.println(`Display name: ${displayName}`)\n\n      // === COALESCING COMPARISONS ===\n\n      a <- 10\n      b <- 20\n      minimum <- a <? b\n      maximum <- a >? b\n      stdout.println(`Minimum: ${minimum}`)\n      stdout.println(`Maximum: ${maximum}`)\n\n      // === CHAINED COALESCING ===\n\n      primary <- String()\n      secondary <- String()\n      fallback <- \"last-resort\"\n      chosen <- primary ?: secondary ?: fallback\n      stdout.println(`Chosen: ${chosen}`)","migrationContext":"Java: ternary x != null ? x : default, no null coalescing operator, Optional.orElse(). Python: x if x is not None else default, or shorthand x or default (falsy gotcha). Rust: unwrap_or(), unwrap_or_else(), no null concept. Go: if x == nil then default, no ternary or coalescing. Kotlin: ?: elvis operator for null, no coalescing comparisons. Swift: ?? nil coalescing (same operator as EK9!), no equivalent of ?: isSet check or <? >? coalescing comparisons. JavaScript: ?? null coalescing (ES2020), || logical or fallback. EK9: ?? (memory check), ?: (isSet check), <? <=? >? >=? (coalescing comparisons), :=? (conditional assignment).","keywords":["absent","assignment","coalescing","conditional","default","elvis","expression","fallback","greater","isset","lesser","migrate","null","operator","safe","swift","unset"],"primaryTopics":["coalescing","null coalescing","default value operator"],"typicalErrors":[{"error":"E04020","correct":"displayName <- name ?: \"Anonymous\"","incorrect":"displayName <- name ?: 42","explanation":"Coalescing operators require both operands to be the same type. Mixing String and Integer triggers E04020 — incompatible types in coalescing expression. See ek9 -h E04020 for details."}],"companions":[]}
{"id":244,"category":"Operators and Expressions","question":"How do boolean and bitwise operators work in EK9?","url":"https://ek9.io/qa/QA0244.html","alternatePhrasings":["What is the difference between boolean and bitwise operators in EK9?","How do shift operators work on Bits in EK9?","Does EK9 use the same syntax for boolean and bitwise operations?"],"answer":"EK9 uses the SAME operator names for both Boolean logic and Bits manipulation. The compiler resolves which implementation to use based on the operand types.\n\nSHARED OPERATORS\nThe 'and', 'or', 'xor' operators work on BOTH Boolean and Bits types. All are pure, take 1 argument, and return a value.\n\nFor Boolean:\n  result <- true and false\n  result <- true or false\n  result <- true xor true\n\nFor Bits:\n  mask <- 0b1100 and 0b1010\n  combined <- 0b1100 or 0b1010\n  toggled <- 0b1100 xor 0b1010\n\nNEGATION (~)\nThe ~ operator works on Boolean (logical NOT) and Bits (bitwise NOT). Numbers use unary - for negation, not ~.\n  notTrue <- ~ true\n  flipped <- ~ 0b1100\n\nBIT SHIFT OPERATORS (<< and >>)\nThe << and >> operators are Bits-only. They shift bits left or right. Both are pure, take 1 Integer argument, and return a new Bits value.\n  shifted <- 0b0001 << 3\n  result <- 0b1000 >> 2\n\nBits values grow as needed with left shift. Right shift discards bits.\n\nBITS ARE NOT INTEGERS\nBits are ordered collections of individual bits. They are NOT integers. You cannot add Bits or use them in arithmetic. They support: and, or, xor, ~ (NOT), << (shift left), >> (shift right), length, ==, <>, $.\n\nBITS LITERALS\nBits literals use the 0b prefix:\n  flags <- 0b010011\n  mask <- 0b111100\n\nSee Q30 for Boolean basics. See Q40 for Bits type. See Q238 for the complete operator set. See Q240 for arithmetic operators.","ek9Example":"defines module qa.operators.booleanbitwise\n\n  defines program\n\n    BooleanBitwiseDemo()\n      stdout <- Stdout()\n\n      // === BOOLEAN LOGIC ===\n\n      a <- true\n      b <- false\n\n      stdout.println(`and: ${a and b}`)\n      stdout.println(`or: ${a or b}`)\n      stdout.println(`xor: ${a xor b}`)\n      stdout.println(`not: ${~ a}`)\n\n      // === BITWISE OPERATIONS (same syntax!) ===\n\n      mask1 <- 0b1100\n      mask2 <- 0b1010\n\n      stdout.println(`Bits and: ${mask1 and mask2}`)\n      stdout.println(`Bits or: ${mask1 or mask2}`)\n      stdout.println(`Bits xor: ${mask1 xor mask2}`)\n      stdout.println(`Bits not: ${~ mask1}`)\n\n      // === BIT SHIFT ===\n\n      oneBit <- 0b0001\n      shifted <- oneBit << 3\n      stdout.println(`Shift left: ${shifted}`)\n\n      wide <- 0b1000\n      narrow <- wide >> 2\n      stdout.println(`Shift right: ${narrow}`)\n\n      // === BITS ARE NOT INTEGERS ===\n\n      flags <- 0b010011\n      stdout.println(`Flags length: ${length flags}`)\n      stdout.println(`Flags equal: ${flags == 0b010011}`)","migrationContext":"Java: && || for boolean, & | ^ for bitwise, different syntax for same concepts. Python: and or not for boolean, & | ^ ~ for bitwise, different syntax. Rust: && || for boolean, & | ^ for bitwise, ! for both not. Go: && || for boolean, & | ^ for bitwise, different syntax. Kotlin: && || for boolean, and or xor for bitwise on Int. JavaScript: && || for boolean, & | ^ for bitwise, different syntax. EK9: 'and' 'or' 'xor' '~' for BOTH Boolean and Bits (same syntax, type-based dispatch), << >> for Bits only.","keywords":["and","binary","bits","bitwise","boolean","expression","flag","left","logic","mask","negate","not","operator","or","right","shift","xor"],"primaryTopics":[],"typicalErrors":[{"error":"E07620","correct":"stdout.println(`not: ${~ a}`)","incorrect":"stdout.println(`not: ${a!}`)","explanation":"Boolean negation in EK9 uses the ~ operator, not ! which is the factorial operator for Integer. The ! operator is not defined on Boolean. See ek9 -h E07620 for details."}],"companions":[]}
{"id":245,"category":"Operators and Expressions","question":"How do I implement all the operators my custom type needs?","url":"https://ek9.io/qa/QA0245.html","alternatePhrasings":["What operators should I implement for a custom class in EK9?","How do I write a complete custom type with all operators?","What is the best practice for implementing operators on EK9 classes?"],"answer":"Choose operators based on how your type will be used. Use 'default operator' for field-by-field behaviour, override for custom logic.\n\nMINIMUM USEFUL SET\nMost types need: == (equality), $ (string), ? (isSet).\n\nCOMPARISON (pure, 1 arg, return Boolean or Integer)\n  operator == as pure -> other as T <- rtn as Boolean\n  operator <=> as pure -> other as T <- rtn as Integer\n\nARITHMETIC (pure, 1 arg, return new value)\n  operator + as pure -> other as T <- rtn as T\n  operator - as pure -> other as T <- rtn as T\n\nUNARY (pure, 0 args)\n  operator - as pure <- rtn as T (negate)\n  operator #^ as pure <- rtn as OtherType (promote)\n\nMUTATION (NOT pure, no return)\n  operator += -> other as T\n  operator :=: -> source as T (copy)\n\nCONVERSION (pure, 0 args, enforced return types)\n  operator $ as pure <- rtn as String\n  operator #? as pure <- rtn as Integer\n  operator ? as pure <- rtn as Boolean\n\nERROR SCENARIOS\n  E07500 - must be pure. E07510 - cannot be pure. E06280/E06290 - wrong param count. E07520/E07550/E07570/E07580 - wrong return type.\n\nSee Q96 for class operator syntax. See Q98 for record copy/merge/replace. See Q116 for default operator. See Q238 for complete operator set. See Q239 for comparison. See Q240 for arithmetic. See Q241 for mutation. See Q242 for conversion.","ek9Example":"defines module qa.operators.customtype\n\n  defines class\n\n    Temperature\n      celsius <- Float()\n\n      default private Temperature() as pure\n\n      Temperature() as pure\n        -> celsius as Float\n        this.celsius :=: celsius\n\n      celsius() as pure\n        <- rtn as Float: Float(celsius)\n\n      // === COMPARISON (pure, 1 arg, return Boolean or Integer) ===\n\n      operator == as pure\n        -> other as Temperature\n        <- rtn as Boolean: celsius == other.celsius\n\n      operator <> as pure\n        -> other as Temperature\n        <- rtn as Boolean: celsius <> other.celsius\n\n      operator <=> as pure\n        -> other as Temperature\n        <- rtn as Integer: celsius <=> other.celsius\n\n      operator < as pure\n        -> other as Temperature\n        <- rtn as Boolean: celsius < other.celsius\n\n      operator <= as pure\n        -> other as Temperature\n        <- rtn as Boolean: celsius <= other.celsius\n\n      operator > as pure\n        -> other as Temperature\n        <- rtn as Boolean: celsius > other.celsius\n\n      operator >= as pure\n        -> other as Temperature\n        <- rtn as Boolean: celsius >= other.celsius\n\n      // === ARITHMETIC (pure, 1 arg, return new value) ===\n\n      operator + as pure\n        -> other as Temperature\n        <- rtn as Temperature: Temperature(celsius + other.celsius)\n\n      operator - as pure\n        -> other as Temperature\n        <- rtn as Temperature: Temperature(celsius - other.celsius)\n\n      // === UNARY (pure, 0 args, return value) ===\n\n      operator - as pure\n        <- rtn as Temperature: Temperature(-celsius)\n\n      operator #^ as pure\n        <- rtn as Float: Float(celsius)\n\n      // === CONVERSION (pure, 0 args, strictly typed returns) ===\n\n      operator $ as pure\n        <- rtn as String: `${celsius}C`\n\n      operator #? as pure\n        <- rtn as Integer: #?celsius\n\n      override operator ? as pure\n        <- rtn as Boolean: celsius?\n\n      // === MUTATION (NOT pure, no return) ===\n\n      operator +=\n        -> other as Temperature\n        celsius += other.celsius\n\n      operator :=:\n        -> source as Temperature\n        celsius :=: source.celsius\n\n  defines program\n\n    CustomTypeDemo()\n      stdout <- Stdout()\n\n      freezing <- Temperature(0.0)\n      boiling <- Temperature(100.0)\n      body <- Temperature(37.0)\n\n      // === COMPARISON ===\n\n      stdout.println(`Equal: ${freezing == boiling}`)\n      stdout.println(`Less: ${freezing < boiling}`)\n      stdout.println(`Compare: ${freezing <=> boiling}`)\n\n      // === ARITHMETIC (creates new values) ===\n\n      sum <- freezing + body\n      stdout.println(`Sum: ${sum}`)\n      diff <- boiling - body\n      stdout.println(`Diff: ${diff}`)\n\n      // === UNARY ===\n\n      negated <- -body\n      stdout.println(`Negated: ${negated}`)\n\n      // === PROMOTE ===\n\n      floatVal as Float: body\n      stdout.println(`Promoted: ${floatVal}`)\n\n      // === CONVERSION ===\n\n      stdout.println(`String: ${body}`)\n      stdout.println(`IsSet: ${body?}`)\n      stdout.println(`Hash: ${#?body}`)\n\n      // === MUTATION ===\n\n      room <- Temperature(20.0)\n      room += Temperature(5.0)\n      stdout.println(`After +=: ${room}`)\n\n      target <- Temperature(0.0)\n      target :=: boiling\n      stdout.println(`After copy: ${target}`)","migrationContext":"Java: Comparable, manual equals/hashCode/toString. Python: __eq__/__lt__/__add__ etc. Rust: derive traits. Go: no operator overloading. EK9: operators with enforced signatures, 'default operator' for field-by-field, override for custom.","keywords":["abstract","class","complete","custom","default","example","expression","immutable","implement","mutation","open","operator","override","practice","pure","side-effect","signature","temperature","type","virtual"],"primaryTopics":[],"typicalErrors":[{"error":"E07500","correct":"operator == as pure","incorrect":"operator ==","explanation":"Comparison operators must be marked 'as pure' because equality checks cannot have side effects. Omitting 'as pure' triggers E07500. See ek9 -h E07500 for details."},{"error":"E07550","correct":"<- rtn as Integer: celsius <=> other.celsius","incorrect":"<- rtn as Boolean: celsius <=> other.celsius","explanation":"The <=> (spaceship) operator must return Integer (negative, zero, or positive for ordering). Returning Boolean instead triggers E07550. See ek9 -h E07550 for details."},{"error":"E07520","correct":"<- rtn as Boolean: celsius == other.celsius","incorrect":"<- rtn as Integer: celsius == other.celsius","explanation":"The == operator must return Boolean. Returning Integer instead of Boolean triggers E07520. See ek9 -h E07520 for details."},{"error":"E50030","correct":"freezing <- Temperature(0.0)","incorrect":"freezing as Temperature: \"cold\"","explanation":"A String literal cannot initialise a Temperature variable. EK9 is strongly typed and requires compatible types. Use the Temperature constructor with a Float argument. See ek9 -h E50030 for details."}],"companions":[],"oracleToolHint":{"tool":"ek9_add_member","intent":"operators","description":"Oracle can add operator declarations to an existing class with correct signatures and purity."}}
{"id":246,"category":"Debugging and Troubleshooting","question":"How do I debug an EK9 program?","url":"https://ek9.io/qa/QA0246.html","alternatePhrasings":["What debugging tools does EK9 provide?","How do I find bugs in my EK9 code?","What is the EK9 debugging workflow?"],"answer":"EK9 provides several strategies for finding and preventing bugs, emphasising compile-time detection over runtime debugging. The compiler catches the majority of defects before your program ever runs.\n\nCOMPILE-TIME DEFECT PREVENTION\nThe compiler is your primary debugging tool. EK9's type system, flow analysis, and enforced guard patterns catch 80-90% of defects at compile time. Error messages include the file, line, column, and a specific error code. Use 'ek9 -E1' for visual error display with source snippets and carets pointing to the exact location. Use 'ek9 -h EXXXXX' to look up any error code for a detailed explanation.\n\nREQUIRE AND ASSERT\nThe 'require' keyword validates preconditions at the start of methods and functions. It is always active and cannot be disabled:\n  require value?\n  require count > 0\nIf the condition fails, an exception is thrown with the source location. Use 'assert' for postconditions and invariants inside method bodies.\n\nDIAGNOSTIC OUTPUT\nUse stdout.println() to print values during development. The $ operator converts any value to a string, and string interpolation with backtick strings makes this easy:\n  stdout.println(`Debug: value=${value}, count=${count}`)\n\nDEBUG INSTRUMENTATION (-cg flag)\nCompile with 'ek9 -cg' to enable debug instrumentation. This embeds source file, line, and column information into assertion messages and exception stack traces. Without -cg, assertion failures show generic messages. With -cg, they show exact source locations like './myfile.ek9:42:5'.\n\nBLACK-BOX TESTING\nEK9 has built-in black-box testing. Create an expected_output.txt file alongside your .ek9 file. Run with 'ek9 -t' and the test runner compares actual output against expected output line by line. This catches regressions without writing any test assertions.\n\nTEST DIRECTIVE\nMark methods with @Test to create unit tests. Use assert, assertThrows, and assertDoesNotThrow inside test methods. Run all tests with 'ek9 -t myfile.ek9'.\n\nLANGUAGE SERVER (LSP)\nRun 'ek9 -ls' for real-time error detection in your editor. The LSP provides hover documentation, go-to-definition, and completions. Errors appear as you type, before you even save the file.\n\nPLATFORM DEBUGGER\nA platform debugger (edb) is planned but not yet available. The compiler generates JSR-45 SMAP data and LocalVariableTable entries so that standard JVM debuggers can step through EK9 source code. In the meantime, use diagnostic output and the testing framework.\n\nSee Q134 for try/catch error handling. See Q29 for tri-state (unset variable) semantics. See Q155 for writing unit tests. See Q247 for reading error messages. See Q252 for verbose compilation modes. See Q322 for profiling to find performance bottlenecks.","ek9Example":"defines module qa.debugging.program\n\n  defines function\n\n    validateScore() as pure\n      -> score as Integer\n      <- rtn as Boolean: false\n\n      require score?\n      require score >= 0\n      require score <= 100\n      rtn: true\n\n  defines class\n\n    GameScore\n      points <- Integer()\n\n      GameScore()\n        -> initialPoints as Integer\n        require initialPoints?\n        require initialPoints >= 0\n        this.points :=: initialPoints\n\n      addPoints()\n        -> amount as Integer\n        require amount?\n        require amount > 0\n        points += amount\n\n      operator $ as pure\n        <- rtn as String: `GameScore(${points})`\n\n      override operator ? as pure\n        <- rtn as Boolean: points?\n\n  defines program\n\n    DebuggingDemo()\n      stdout <- Stdout()\n\n      // Diagnostic output with $ operator\n      score <- GameScore(10)\n      stdout.println(`Initial: ${score}`)\n\n      score.addPoints(5)\n      stdout.println(`After adding 5: ${score}`)\n\n      // Validation with require\n      valid <- validateScore(85)\n      stdout.println(`Score 85 valid: ${valid}`)\n\n      // Try/catch for error handling\n      try\n        badScore <- GameScore(-1)\n        stdout.println(`Should not reach: ${badScore}`)\n      catch\n        -> ex as Exception\n        stdout.println(`Caught: ${ex.reason()}`)\n\n      // Guard pattern for safe access\n      parsed <- Integer(\"42\")\n      if parsed?\n        stdout.println(`Parsed value: ${parsed}`)\n\n      badParsed <- Integer(\"not a number\")\n      if badParsed?\n        stdout.println(\"Should not print\")\n      else\n        stdout.println(\"Invalid input detected via ? check\")","migrationContext":"Java: IDE debuggers (IntelliJ, Eclipse), System.out.println, JUnit assertions, stack traces. Python: pdb debugger, print(), pytest, traceback. Rust: dbg!() macro, println!(), cargo test, LLDB/GDB. Go: fmt.Println, Delve debugger, go test. C++: GDB/LLDB, cerr, assert macro. Kotlin: IDE debuggers, println, JUnit. EK9: require/assert always active, stdout.println with $ operator, -cg debug instrumentation, built-in black-box testing, LSP real-time errors, platform debugger planned.","keywords":["assert","bug","debug","debugging","diagnostic","error","error-message","instrumentation","lsp","output","println","program","require","testing","trace","troubleshoot"],"primaryTopics":["debug","debugger","debug program"],"typicalErrors":[{"error":"E50001","correct":"valid <- validateScore(85)","incorrect":"unused <- validateScore(85)","explanation":"If a declared variable is never referenced after assignment, the compiler rejects it. Every variable must be used. See ek9 -h E50001 for details."}],"companions":[]}
{"id":247,"category":"Debugging and Troubleshooting","question":"How do I read EK9 compiler error messages?","url":"https://ek9.io/qa/QA0247.html","alternatePhrasings":["What do EK9 error messages look like?","How do I understand EK9 compiler output?","What are EK9 error verbosity levels?"],"answer":"EK9 error messages follow a consistent format and can be displayed at multiple verbosity levels. Understanding the format helps you fix issues quickly.\n\nERROR MESSAGE FORMAT\nEvery error follows this pattern:\n  filename.ek9:line:column: EXXXXX description\nFor example:\n  myfile.ek9:15:3: E50030 'unknownVar' is not resolved\nThe file path, line number, and column number tell you exactly where the error is. The EXXXXX code uniquely identifies the error type.\n\nVERBOSITY LEVELS\nEK9 supports five error verbosity levels, controlled by -E0 through -E4:\n\n-E0 (Minimal): Single-line error messages. Just the location and error code. Best for experienced developers who know what the codes mean.\n\n-E1 (Visual): Rust-style visual display with source code snippets and carets (^) pointing to the exact error location. Shows the surrounding code context. Recommended for daily development.\n\n-E2 (Suggestions): Everything in -E1 plus 'Did you mean?' suggestions. When you misspell a variable or method name, the compiler uses fuzzy matching to suggest corrections.\n\n-E3 (Full Explanations): Everything in -E2 plus detailed explanations suitable for beginners and AI tools. Includes diagnosis (what went wrong), rationale (why EK9 enforces this), and examples (how to fix it). Best for learning EK9.\n\n-E4 (Developer): Internal compiler diagnostics. Shows phase-level detail. Only useful for compiler developers.\n\nLOOKING UP ERROR CODES\nAny error code can be looked up with:\n  ek9 -h EXXXXX\nThis displays a detailed explanation of the error, what causes it, and how to fix it. Works offline, no internet needed.\n\nMULTIPLE ERRORS\nThe compiler reports ALL errors it can find in a single pass, not just the first one. Fix errors from the top of the file downward, as later errors are sometimes caused by earlier ones.\n\nSee Q246 for debugging strategies. See Q248 for error code ranges and lookup. See Q252 for all compiler flags. See Q311 for the full catalog of quality error codes.","ek9Example":"defines module qa.debugging.errormessages\n\n  defines class\n\n    Temperature\n      degrees <- Float()\n      unit <- String()\n\n      Temperature()\n        ->\n          initialValue as Float\n          initialUnit as String\n        require initialValue?\n        require initialUnit?\n        this.degrees :=: initialValue\n        this.unit :=: initialUnit\n\n      celsius() as pure\n        <- rtn as Float: Float()\n        if unit == \"C\"\n          rtn: degrees\n        else\n          rtn: (degrees - 32.0) * 5.0 / 9.0\n\n      operator $ as pure\n        <- rtn as String: `${degrees} ${unit}`\n\n      override operator ? as pure\n        <- rtn as Boolean: degrees? and unit?\n\n  defines program\n\n    ErrorMessageDemo()\n      stdout <- Stdout()\n\n      // Create valid temperature\n      reading <- Temperature(100.0, \"C\")\n      stdout.println(`Temperature: ${reading}`)\n      stdout.println(`In Celsius: ${reading.celsius()}`)\n\n      // Demonstrate correct patterns that avoid common errors\n      count <- 42\n      stdout.println(`Count: ${count}`)\n\n      // Correct type usage\n      name <- \"EK9\"\n      nameLength <- length name\n      stdout.println(`${name} has ${nameLength} characters`)\n\n      // Correct operator usage\n      a <- 10\n      b <- 20\n      sum <- a + b\n      stdout.println(`Sum: ${sum}`)","migrationContext":"Java: javac shows file:line: error with no visual display, IDEs provide rich error display. Python: SyntaxError with caret, traceback for runtime errors. Rust: famously rich error messages with source snippets, suggestions, and explanations. Go: terse single-line errors. C++: notoriously verbose template errors, varies by compiler. Kotlin: similar to Java with better type inference errors. EK9: five verbosity levels from minimal to full AI-friendly explanations, visual source snippets with carets, fuzzy suggestions, offline error code lookup.","keywords":["E0","E1","E2","E3","code","compile","debug","diagnostic","error","error-message","format","level","lookup","message","migrate","read","troubleshoot","understand","verbosity"],"primaryTopics":["error message","compiler error","read error"],"typicalErrors":[{"error":"E50060","correct":"length name","incorrect":"name.size()","explanation":"EK9 uses 'length' as a prefix operator, not a method call. The method 'length()' does not exist on String. See ek9 -h E50060 for details."}],"companions":[]}
{"id":248,"category":"Debugging and Troubleshooting","question":"What does a specific EK9 error code mean?","url":"https://ek9.io/qa/QA0248.html","alternatePhrasings":["How do I look up an EK9 error code?","What are the EK9 error code ranges?","How do I find what EXXXXX means in EK9?"],"answer":"Every EK9 compiler error has a unique code in the format EXXXXX (E followed by five digits). Use 'ek9 -h EXXXXX' to look up any error code for a detailed explanation.\n\nLOOKING UP ERROR CODES\nRun the following command to see a detailed explanation:\n  ek9 -h E50030\nThis works offline and shows what the error means, what causes it, and how to fix it.\n\nERROR CODE RANGES\nError codes are organised into ranges by category:\n\nE01xxx - Names and Identifiers\n  E01010: Invalid identifier name\n  E01020: Reserved word used as identifier\n  E01030: Excluded keyword (break, continue, return do not exist in EK9)\n\nE02xxx - Duplicate Definitions\n  E02010: Duplicate type definition\n  E02020: Duplicate method or property name\n  E02030: Duplicate module definition\n\nE04xxx - Type Constraints\n  E04010: Type must extend Exception (for throw/catch)\n  E04020: Not a valid candidate for this context\n\nE05xxx - Type Hierarchy\n  E05020: Circular type hierarchy detected\n  E05030: Type is not open for extension (closed by default)\n  E05120: Method must use override keyword (overriding base method)\n\nE06xxx - Parameters and Arguments\n  E06260: Parameter type mismatch\n  E06280: Too many parameters for this operator or method\n  E06290: Too few parameters\n  E06330: Function signature does not match\n\nE07xxx - Operators and Methods\n  E07500: Operator must be pure but is not declared pure\n  E07510: Operator cannot be pure (mutation operators)\n  E07520: Incorrect return type for operator\n  E07550: Comparison operator must return Boolean\n  E07570: Hash operator must return Integer\n  E07580: String operator must return String\n  E07620: Operator not defined on this type\n\nE08xxx - Flow Analysis and DI\n  E08020: Variable used before it is initialised\n  E08030: Variable not checked with ? before access\n  E08050: Return variable not initialised on all paths\n  E08190: Circular dependency injection detected\n\nE11xxx - Code Quality\n  E11031: Variable name matches common convention violation\n  E11032: Variable name is a reserved word\n  E11040: Method complexity exceeds threshold\n\nE50xxx - Resolution\n  E50030: Symbol not resolved (variable, type, method, or function not found)\n\nCOMMON FIRST ERRORS\nThe most common errors for new EK9 developers are:\n  E50030: Check spelling and ensure imports are correct\n  E05030: Mark the base type 'as open' if you intend to extend it\n  E08020: Initialise the variable before using it\n  E06280/E06290: Check operator parameter counts\n\nSee Q247 for understanding error message format. See Q252 for verbose compilation modes. See Q238 for the complete operator set and enforcement rules. See Q290 for E11031 and E11032 naming errors. See Q291 for naming error troubleshooting. See Q311 for the complete quality checks catalog.","ek9Example":"defines module qa.debugging.errorcodelookup\n\n  defines class\n\n    Account\n      name <- String()\n      balance <- Float()\n\n      Account()\n        ->\n          accountName as String\n          initialBalance as Float\n        require accountName?\n        require initialBalance?\n        require initialBalance >= 0.0\n        this.name :=: accountName\n        this.balance :=: initialBalance\n\n      deposit()\n        -> amount as Float\n        require amount?\n        require amount > 0.0\n        balance += amount\n\n      getBalance() as pure\n        <- rtn as Float: balance\n\n      operator $ as pure\n        <- rtn as String: `Account(${name}, ${balance})`\n\n      operator == as pure\n        -> other as Account\n        <- rtn as Boolean: name == other.name\n\n      operator <=> as pure\n        -> other as Account\n        <- rtn as Integer: balance <=> other.balance\n\n      override operator ? as pure\n        <- rtn as Boolean: name? and balance?\n\n  defines program\n\n    ErrorCodeDemo()\n      stdout <- Stdout()\n\n      // Correctly constructed class with operators\n      acc <- Account(\"Alice\", 100.0)\n      stdout.println(`Account: ${acc}`)\n\n      acc.deposit(50.0)\n      stdout.println(`After deposit: ${acc}`)\n      stdout.println(`Balance: ${acc.getBalance()}`)\n\n      // Correct comparison usage\n      acc2 <- Account(\"Bob\", 200.0)\n      stdout.println(`Equal: ${acc == acc2}`)\n      stdout.println(`Compare: ${acc <=> acc2}`)","migrationContext":"Java: javac error codes are not standardised, IDEs provide error databases. Python: no error code system, SyntaxError and named exceptions. Rust: E0001-E0999 error codes with 'rustc --explain E0308'. Go: no standardised error codes, terse messages. C++: compiler-specific error codes (GCC, Clang, MSVC all different). Kotlin: no standardised error codes, IDE-centric. EK9: unified EXXXXX error codes across all phases, offline lookup with 'ek9 -h EXXXXX', organised by category ranges.","keywords":["E05030","E08020","E50030","EXXXXX","category","code","debug","error","error-message","explain","help","lookup","meaning","range","resolve","troubleshoot"],"primaryTopics":[],"typicalErrors":[{"error":"E50060","correct":"acc.deposit(50.0)","incorrect":"acc.withdraw(50.0)","explanation":"Calling a method that does not exist on the type produces a resolution error. The Account class defines 'deposit' but not 'withdraw'. See ek9 -h E50060 for details."},{"error":"E08090","correct":"stdout.println(`Balance: ${acc.getBalance()}`)","incorrect":"balance <- acc.getBalance()","explanation":"If 'balance' is declared but never used in any expression or output, the compiler rejects it as unused. See ek9 -h E08090 for details."}],"companions":[]}
{"id":249,"category":"Debugging and Troubleshooting","question":"How do I use the language server for error detection?","url":"https://ek9.io/qa/QA0249.html","alternatePhrasings":["What is the EK9 language server?","How do I get real-time errors in my editor?","Does EK9 support IDE integration?"],"answer":"EK9 includes a full Language Server Protocol (LSP) implementation that provides real-time error detection, hover documentation, go-to-definition, and completions in your editor.\n\nSTARTING THE LSP\nTwo modes are available:\n  ek9 -ls     Full LSP mode for IDE integration\n  ek9 -lsh    Hover-help-only mode for lightweight editors\nThe LSP communicates over stdio using the standard LSP JSON-RPC protocol. Any editor that supports LSP can use it (VSCode, Neovim, Emacs, Sublime Text, etc.).\n\nVSCODE EXTENSION\nA VSCode extension is available that provides:\n  Syntax highlighting for .ek9 files\n  Real-time error detection as you type\n  Hover documentation showing type information and method signatures\n  Go-to-definition for navigating to symbol declarations\n  Code completions for types, methods, and operators\n\nREAL-TIME ERROR DETECTION\nThe LSP compiles your code through the PRE_IR_CHECKS phase (phase 9 of 22). This means ALL type errors, flow analysis errors, duplicate detection, and resolution errors appear instantly in your editor. You see errors before you save the file.\n\nHOVER DOCUMENTATION\nHovering over any symbol shows its type, documentation, and available methods. For built-in types, this includes the full API. For your own types, it shows the declared operators and methods.\n\nPROGRESSIVE ERROR DETECTION\nThe multi-phase pipeline means errors are detected progressively:\n  Phase 2-3: Duplicate names and definitions\n  Phase 4-5: Unresolved references and types\n  Phase 6-7: Type hierarchy and generic resolution errors\n  Phase 8-9: Flow analysis (unset variables, uninitialised returns)\nEach phase builds on the previous, so fixing early errors often resolves later ones.\n\nCOMMAND LINE ALTERNATIVE\nIf you prefer the command line, use:\n  ek9 -c myfile.ek9          compile and check for errors\n  ek9 -E1 -c myfile.ek9      visual errors with source snippets\n  ek9 -E2 -c myfile.ek9      add fuzzy suggestions\n\nSee Q246 for debugging strategies. See Q252 for verbose compilation modes. See Q253 for understanding compiler phases.","ek9Example":"defines module qa.debugging.languageserver\n\n  defines trait\n\n    Describable\n      describe() as pure abstract\n        <- rtn as String?\n\n  defines class\n\n    Point\n      x <- Float()\n      y <- Float()\n\n      Point()\n        ->\n          px as Float\n          py as Float\n        this.x :=: px\n        this.y :=: py\n\n      distanceTo() as pure\n        -> other as Point\n        <- rtn as Float: Float()\n        dx <- x - other.x\n        dy <- y - other.y\n        rtn: sqrt (dx * dx + dy * dy)\n\n      operator $ as pure\n        <- rtn as String: `(${x}, ${y})`\n\n      override operator ? as pure\n        <- rtn as Boolean: x? and y?\n\n    Sensor with trait of Describable\n      name <- String()\n      reading <- Float()\n\n      Sensor()\n        ->\n          sensorName as String\n          sensorReading as Float\n        this.name :=: sensorName\n        this.reading :=: sensorReading\n\n      override describe() as pure\n        <- rtn as String: `Sensor ${name}: ${reading}`\n\n      operator $ as pure\n        <- rtn as String: `${name}=${reading}`\n\n      override operator ? as pure\n        <- rtn as Boolean: name? and reading?\n\n  defines program\n\n    LanguageServerDemo()\n      stdout <- Stdout()\n\n      // Types that the LSP would provide hover info for\n      p1 <- Point(0.0, 0.0)\n      p2 <- Point(3.0, 4.0)\n\n      stdout.println(`Point 1: ${p1}`)\n      stdout.println(`Point 2: ${p2}`)\n      stdout.println(`Distance: ${p1.distanceTo(p2)}`)\n\n      // Sensor with trait\n      sensor <- Sensor(\"Temp\", 22.5)\n      stdout.println(`Sensor: ${sensor}`)\n      stdout.println(`Description: ${sensor.describe()}`)","migrationContext":"Java: IntelliJ/Eclipse provide rich IDE support, separate from compiler. Python: Pylance/Pyright LSP servers, type checking optional. Rust: rust-analyzer provides excellent LSP support. Go: gopls is the official LSP server. C++: clangd provides LSP, but C++ complexity limits accuracy. Kotlin: IntelliJ native support, kotlin-language-server for other editors. EK9: built-in LSP as part of the compiler itself, compiles through 10 phases for comprehensive error detection, hover documentation, go-to-definition.","keywords":["completion","debug","definition","detection","editor","error","error-message","hover","ide","integration","language","lsp","realtime","server","troubleshoot","vscode"],"primaryTopics":["language server","LSP","IDE support"],"typicalErrors":[{"error":"E50060","correct":"stdout.println(`Distance: ${p1.distanceTo(p2)}`)","incorrect":"stdout.println(`Distance: ${p1.distance(p2)}`)","explanation":"The method is named 'distanceTo' not 'distance'. Calling a method that does not exist on the type produces a resolution error. See ek9 -h E50060 for details."},{"error":"E08090","correct":"stdout.println(`Sensor: ${sensor}`)","incorrect":"sensorLabel <- `Sensor: ${sensor}`","explanation":"If the variable 'sensor' is declared but never used in any expression, the compiler rejects it. Every declared variable must be referenced. See ek9 -h E50050 for details."}],"companions":[]}
{"id":250,"category":"Debugging and Troubleshooting","question":"Why is the compiler reporting a type mismatch?","url":"https://ek9.io/qa/QA0250.html","alternatePhrasings":["How do I fix type mismatch errors in EK9?","What causes type errors in EK9?","How do I convert between types correctly in EK9?"],"answer":"Type mismatch errors occur when a value of one type is used where another is expected. EK9 has no implicit coercion; all conversions must be explicit.\n\nCOMMON FIXES\n\nInteger to Float: #^ promote or assignment:\n  floatVal <- #^ intVal\n  floatVal as Float: intVal\n\nAny to String: $ operator or interpolation:\n  text <- $count\n  message <- `Count is ${count}`\n\nString to Integer/Float: constructors (unset if invalid):\n  parsed <- Integer(\"42\")\n\nParameter Mismatch: types must match exactly:\n  WRONG:  processFloat(42)\n  RIGHT:  processFloat(#^ 42) or processFloat(42.0)\n\nOperator Return Types (enforced):\n  == returns Boolean (E07550), $ returns String (E07580), #? returns Integer (E07570), <=> returns Integer (E07520). Mutation operators must NOT return (E07410).\n\nDISPATCHER PATTERN\nFor type-specific processing, use dispatcher instead of casting. Runtime routes to the most specific overload.\n\nSee Q24 for type conversion and dispatcher. See Q25 for promote operator. See Q238 for operator rules. See Q242 for conversion operators.","ek9Example":"defines module qa.debugging.typemismatch\n\n  defines function\n\n    processFloat() as pure\n      -> floatInput as Float\n      <- rtn as String: `Processed: ${floatInput}`\n\n  defines class\n\n    Measurement\n      reading <- Float()\n      label <- String()\n\n      Measurement()\n        ->\n          initialReading as Float\n          measureLabel as String\n        this.reading :=: initialReading\n        this.label :=: measureLabel\n\n      operator $ as pure\n        <- rtn as String: `${label}: ${reading}`\n\n      operator == as pure\n        -> other as Measurement\n        <- rtn as Boolean: reading == other.reading\n\n      operator <=> as pure\n        -> other as Measurement\n        <- rtn as Integer: reading <=> other.reading\n\n      operator #? as pure\n        <- rtn as Integer: #? reading\n\n      override operator ? as pure\n        <- rtn as Boolean: reading? and label?\n\n  defines program\n\n    TypeMismatchDemo()\n      stdout <- Stdout()\n\n      // Integer to Float promotion with #^\n      intVal <- 42\n      floatVal <- #^ intVal\n      stdout.println(`Promoted: ${floatVal}`)\n\n      // Direct Float assignment (compiler inserts promotion)\n      widened as Float: intVal\n      stdout.println(`Widened: ${widened}`)\n\n      // Using promoted value in function call\n      result <- processFloat(#^ intVal)\n      stdout.println(result)\n\n      // String conversion with $ operator\n      count <- 100\n      countText <- $count\n      stdout.println(`As string: ${countText}`)\n\n      // Constructor conversion\n      parsed <- Integer(\"99\")\n      if parsed?\n        stdout.println(`Parsed: ${parsed}`)\n\n      // Invalid parse produces unset\n      bad <- Integer(\"abc\")\n      if bad?\n        stdout.println(\"Should not print\")\n      else\n        stdout.println(\"Invalid parse detected\")\n\n      // Correct operator usage\n      m1 <- Measurement(10.0, \"Width\")\n      m2 <- Measurement(20.0, \"Height\")\n      stdout.println(`${m1}`)\n      stdout.println(`Equal: ${m1 == m2}`)\n      stdout.println(`Compare: ${m1 <=> m2}`)","migrationContext":"Java: implicit widening, ClassCastException. Python: dynamic typing, TypeError. Rust/Go: no implicit conversions. EK9: explicit #^ promotion, $ to string, constructor conversion, dispatcher for type-specific logic.","keywords":["cast","coerce","compile","conversion","debug","error","error-message","explicit","fix","implicit","mismatch","operator","parameter","promote","return","troubleshoot","type"],"primaryTopics":[],"typicalErrors":[{"error":"E06270","correct":"result <- processFloat(#^ intVal)","incorrect":"result <- processFloat(\"42\")","explanation":"A String cannot be passed where Float is expected. There is no automatic promotion from String to Float. Use a Float literal or the #^ operator to promote Integer to Float. See ek9 -h E06270 for details."}],"companions":[]}
{"id":251,"category":"Debugging and Troubleshooting","question":"Why does the compiler say my variable is unset?","url":"https://ek9.io/qa/QA0251.html","alternatePhrasings":["How do I fix unset variable errors in EK9?","What does 'used before initialised' mean in EK9?","How do I handle unset values in EK9?"],"answer":"The EK9 compiler uses flow analysis to track whether variables have been initialised. If you use a variable before giving it a value, or access a value without checking if it is set, the compiler reports an error.\n\nCOMMON ERRORS AND FIXES\n\nE08020 - Used Before Initialised:\nYou declared a variable but used it before assigning a value:\n  count <- Integer()     unset\n  total <- count + 1     ERROR: count used before initialised\nFix: assign a value first, or use a guard:\n  count <- Integer()\n  count :=? 0\n  total <- count + 1     now safe\n\nE08030 - Not Checked Before Access:\nYou accessed a value that might be unset without a ? guard:\n  parsed <- Integer(someString)\n  stdout.println($parsed)     ERROR: parsed not checked\nFix: wrap in a guard:\n  parsed <- Integer(someString)\n  if parsed?\n    stdout.println($parsed)   safe\n\nE08050 - Return Not Always Initialised:\nYour function has code paths where the return value is never set:\n  calculateArea()\n    -> shape as String\n    <- rtn as Float: Float()\n    if shape == \"circle\"\n      rtn: 3.14\n    // ERROR: rtn not set when shape is not circle\nFix: ensure ALL paths set the return value.\n\nGUARD EXPRESSIONS\nThe preferred way to handle potentially unset values is with guard expressions. These combine assignment with a ? check:\n  if value <- computeSomething()\n    stdout.println(`Got: ${value}`)\nThe block only executes if value is set. This works identically in if, switch, for, while, and try.\n\nGUARDED ASSIGNMENT (:=?)\nThe :=? operator only assigns if the target is currently unset:\n  name <- String()\n  name :=? \"default\"\nThis is safe because :=? checks first.\n\nCOALESCING OPERATORS\nThe ?? operator provides a default when a value is unset:\n  safe <- maybeUnset ?? \"fallback\"\nThe ?: operator evaluates a function when unset:\n  safe <- maybeUnset ?: getDefault\n\nOPTIONAL AND RESULT\nOptional requires a ? check before calling .get(). Result requires isOk() before .ok() and isError() before .error(). The compiler enforces these guards at compile time with no escape hatches.\n\nDIVISION BY ZERO\nDividing by zero in EK9 does NOT throw an exception. It returns an unset value:\n  result <- 10 / 0\n  // result is unset, not an error\n  if result?\n    stdout.println($result)\n  else\n    stdout.println(\"Division produced unset\")\n\nSee Q29 for tri-state semantics. See Q47 for Optional safe access. See Q48 for Result safe access. See Q75 for guard patterns in switch. See Q243 for coalescing operators. See Q632 for definition ordering. See Q633 for branch initialization. See Q634 for guard-based safe access.","ek9Example":"defines module qa.debugging.variableunset\n\n  defines function\n\n    safeDivide() as pure\n      ->\n        a as Integer\n        b as Integer\n      <- rtn <- Integer()\n\n      if b <> 0\n        rtn: a / b\n\n    findItem() as pure\n      -> items as List of String\n      <- rtn <- Optional() of String\n\n      for item in items\n        if item == \"target\"\n          rtn: Optional(item)\n\n  defines program\n\n    VariableUnsetDemo()\n      stdout <- Stdout()\n\n      // Guard expression: only executes if set\n      if result <- safeDivide(10, 3)\n        stdout.println(`Division result: ${result}`)\n\n      // Division by zero returns unset\n      if zeroResult <- safeDivide(10, 0)\n        stdout.println(\"Should not print\")\n      else\n        stdout.println(\"Division by zero produced unset\")\n\n      // Guarded assignment with :=?\n      name <- String()\n      stdout.println(`Before guard: set=${name?}`)\n      name :=? \"default\"\n      stdout.println(`After guard: ${name}`)\n      name :=? \"other\"\n      stdout.println(`After second guard: ${name}`)\n\n      // Coalescing operator ??\n      unsetVal <- String()\n      safe <- unsetVal ?? \"fallback\"\n      stdout.println(`Coalesced: ${safe}`)\n\n      // Optional with guard\n      items <- [\"apple\", \"target\", \"cherry\"]\n      if found <- findItem(items)\n        foundItem <- found.get()\n        stdout.println(`Found: ${foundItem}`)\n\n      // Integer parsing with guard\n      parsed <- Integer(\"42\")\n      if parsed?\n        stdout.println(`Parsed: ${parsed}`)\n\n      badParsed <- Integer(\"xyz\")\n      if badParsed?\n        stdout.println(\"Should not print\")\n      else\n        stdout.println(\"Invalid input: integer is unset\")","migrationContext":"Java: null by default, NullPointerException at runtime, no compile-time null tracking (unless using annotations). Python: NameError for undefined, None checks are convention not enforced. Rust: no null, Option/Result with compiler-enforced pattern matching. Go: zero values by default (0, \"\", nil), nil pointer panics at runtime. Kotlin: nullable types with compile-time null safety, but !! escape hatch exists. Swift: Optional with compile-time enforcement, ! force-unwrap crashes. EK9: tri-state model, compile-time flow analysis tracks initialisation, guard expressions, :=? conditional assignment, no escape hatches.","keywords":["E08020","E08030","E08050","analysis","before","check","compile","debug","error-message","flow","guard","initialise","initialize","isset","null-safe","safe","set","troubleshoot","unset","used","variable"],"primaryTopics":[],"typicalErrors":[{"error":"E50060","correct":"stdout.println(`Before guard: set=${name?}`)","incorrect":"stdout.display(`Before guard: set=${name?}`)","explanation":"Stdout does not have a display() method. The correct method is println(). Calling a non-existent method triggers E50060 — method not resolved. See ek9 -h E50060 for details."}],"companions":[]}
{"id":252,"category":"Debugging and Troubleshooting","question":"How do I use verbose or debug compilation modes?","url":"https://ek9.io/qa/QA0252.html","alternatePhrasings":["What compiler flags does EK9 support?","How do I get more detail from the EK9 compiler?","What are the EK9 compilation modes?"],"answer":"EK9 provides a comprehensive set of command-line flags for controlling compilation verbosity, debugging, and output.\n\nERROR VERBOSITY (-E0 through -E4)\n  -E0    Minimal: single-line error messages with location and code\n  -E1    Visual: Rust-style display with source snippets and caret markers\n  -E2    Suggestions: visual plus 'Did you mean?' fuzzy matching\n  -E3    Full: detailed explanations with diagnosis, rationale, and examples\n  -E4    Developer: internal compiler diagnostics for compiler developers\nDefault is -E1. Use -E3 when learning EK9 or when an error is confusing.\n\nCOMPILATION MODES\n  -c     Incremental compile (only recompile changed files)\n  -C     Full recompile (recompile everything from scratch)\n  -r     Compile and run the program\n  -t     Run tests (@Test methods and expected_output.txt)\nIncremental compilation is faster for large projects. Use -C when you suspect stale cached output.\n\nDEBUG AND VERBOSE FLAGS\n  -v     Verbose output: shows which files are being compiled and which phases complete\n  -dv    Debug verbose: more detail including symbol resolution and phase timing\n  -cg    Debug instrumentation: embeds source location (file:line:column) in assertion messages and stack traces\n  -cd    Combined development and debug mode\nThe -cg flag is essential for meaningful assertion failure messages. Without it, assertion failures show generic text. With it, they show exact source locations.\n\nANALYSIS FLAGS\n  -di    DI/AOP analysis: outputs dependency injection wiring and aspect pointcut information\n  -Cp N  Stop at phase N: compiles up to the specified phase number and stops. Not typical for end users, but valuable for AI tools learning EK9 (verify syntax and types compile correctly without generating runnable programs) and for compiler developers\n\nHELP AND INFORMATION\n  -h keyword    Help for a built-in type (e.g., 'ek9 -h String', 'ek9 -h List')\n  -h EXXXXX     Explain a specific error code\n  -H            List all available help keywords\n  -V            Show compiler version\n\nTEST OUTPUT FORMATS\n  -t0    Plain text test output\n  -t1    JUnit XML output\n  -t2    TAP format\n  -t3    JSON output\n  -t4    Markdown output\n  -t5    CSV output\n  -t6    HTML dashboard\nThe default is -t0 (plain text). Use -t1 for CI/CD integration.\n\nCOVERAGE\n  -tc SET      Statement coverage tracking\n  -tc COUNT    Execution count per statement\n  -tc ATOMIC   Thread-safe execution counting\nCoverage data is written to a coverage report. The default threshold is 80%.\n\nSee Q246 for debugging strategies. See Q247 for reading error messages. See Q253 for understanding compiler phases. See Q323 for AI quality workflow. See Q622 for formatting flags. See Q627 for profiling flags.","ek9Example":"defines module qa.debugging.verbosemodes\n\n  defines function\n\n    describeFeature()\n      ->\n        featureEnabled as Boolean\n        featureName as String\n      <- description as String: String()\n\n      if featureEnabled\n        description: `${featureName} is enabled`\n      else\n        description: `${featureName} is disabled`\n\n  defines program\n\n    VerboseModesDemo()\n      stdout <- Stdout()\n\n      // Simple program to demonstrate compilation\n      stdout.println(\"Compilation modes demo\")\n\n      // Use string interpolation\n      version <- \"1.0\"\n      stdout.println(`Version: ${version}`)\n\n      // Simple arithmetic\n      a <- 10\n      b <- 20\n      stdout.println(`Sum: ${a + b}`)\n\n      // Boolean check via function parameter\n      stdout.println(describeFeature(true, \"Logging\"))\n\n      // List operations\n      items <- [\"alpha\", \"beta\", \"gamma\"]\n      for item in items\n        stdout.println(`Item: ${item}`)","migrationContext":"Java: javac has minimal flags, IDEs provide most analysis. Python: python -v for verbose, -W for warnings, no compilation phases. Rust: RUST_LOG for verbose, cargo build --verbose, rustc --explain. Go: go build -v for verbose, go vet for analysis. C++: compiler-specific flags (-Wall, -Wextra, -g for debug info). Kotlin: kotlinc has minimal flags, relies on IDE. EK9: five error verbosity levels, debug instrumentation, phase-level control, built-in test formats, coverage modes, offline error code lookup.","keywords":["E0","E1","E2","E3","cg","compile","coverage","debug","error-message","flag","instrumentation","migrate","mode","output","phase","test","troubleshoot","verbose"],"primaryTopics":[],"typicalErrors":[{"error":"E08090","correct":"stdout.println(describeFeature(true, \"Logging\"))","incorrect":"featureDesc <- describeFeature(true, \"Logging\")","explanation":"If 'featureDesc' is declared but never used anywhere, the compiler rejects it as unused. Every variable must be referenced. See ek9 -h E08090 for details."}],"companions":[]}
{"id":253,"category":"Debugging and Troubleshooting","question":"What do the compiler phases mean?","url":"https://ek9.io/qa/QA0253.html","alternatePhrasings":["How many compilation phases does EK9 have?","What happens during EK9 compilation?","Why does EK9 use a multi-phase compiler?"],"answer":"EK9 uses a 22-phase compilation pipeline. Each phase performs a specific task and builds on the results of previous phases. Understanding the phases helps you interpret error messages and know when different types of errors are detected.\n\nFRONTEND PHASES (0-9)\nThese phases read, parse, and validate your source code:\n\nPhase 0 - READING: Reads source files from disk.\nPhase 1 - PARSING: Parses EK9 source into an abstract syntax tree (AST) using the ANTLR4 grammar. Syntax errors (missing colons, bad indentation) are caught here.\nPhase 2 - SYMBOL_DEFINITION: Creates the symbol table with all defined types, classes, functions, and variables.\nPhase 3 - DUPLICATION_CHECK: Detects duplicate type names, duplicate methods, and duplicate properties within the same scope.\nPhase 4 - REFERENCE_CHECKS: Validates that all referenced symbols (types, variables, functions) exist somewhere in the codebase.\nPhase 5 - EXPLICIT_TYPE_SYMBOL_DEFINITION: Second pass to define and resolve non-inferred template types (generic type parameters).\nPhase 6 - TYPE_HIERARCHY_CHECKS: Validates inheritance hierarchies, checks for circular inheritance, verifies 'as open' for extension, and validates override correctness.\nPhase 7 - FULL_RESOLUTION: Third pass to define and resolve inferred types and template types. Generics are fully resolved here.\nPhase 8 - POST_RESOLUTION_CHECKS: Validates all symbols and template types are fully resolved and consistent.\nPhase 9 - PRE_IR_CHECKS: Code flow analysis. Detects unset variables (E08020), missing guards (E08030), uninitialised returns (E08050), and validates DI wiring. This is the most important phase for catching logic errors.\n\nMIDDLE PHASES (10-13)\nPhase 10 - PLUGIN_RESOLUTION: Resolves external plugin points.\nPhase 11 - IR_GENERATION: Generates the intermediate representation (IR) from the validated AST. The IR is the common format for all backends.\nPhase 12 - IR_ANALYSIS: Analyses the IR for optimisation opportunities.\nPhase 13 - IR_OPTIMISATION: Applies IR-level optimisations (currently stub implementation).\n\nBACKEND PHASES (14-21)\nPhase 14 - CODE_GENERATION_PREPARATION: Prepares for target code generation.\nPhase 15 - CODE_GENERATION_CONSTANTS: Generates code for constant values.\nPhase 16 - CODE_GENERATION_APPLICATIONS: Generates code for application entry points.\nPhase 17 - CODE_GENERATION_AGGREGATES: Generates code for classes, records, traits, and other aggregates.\nPhase 18 - CODE_OPTIMISATION: Applies target-specific code optimisations.\nPhase 19 - PLUGIN_LINKAGE: Links external plugins into the compiled output.\nPhase 20 - APPLICATION_PACKAGING: Packages the compiled application.\nPhase 21 - PACKAGING_POST_PROCESSING: Final post-processing and cleanup.\n\nWHERE ERRORS ARE CAUGHT\nMost developer errors are caught in phases 2-9 (the frontend). These phases run quickly and provide immediate feedback. The LSP runs through phase 9, which is why you get comprehensive error detection in your editor.\n\nPHASE CONTROL\nThe '-Cp N' flag stops compilation at phase N. This is not something end users typically need, but it is valuable in two scenarios: AI tools learning EK9 can verify that syntax and types compile correctly without generating runnable programs, and compiler developers can isolate specific phases for testing. For example, '-Cp 9' runs all validation without code generation.\n\nWHY MULTI-PHASE?\nA multi-phase approach allows each phase to assume that previous phases have validated their concerns. Phase 9 (flow analysis) does not need to check for duplicate names because phase 3 already did that. This makes each phase simpler, more focused, and more maintainable. It also enables the LSP to stop at phase 9 for fast feedback.\n\nSee Q246 for debugging strategies. See Q249 for language server integration. See Q252 for verbose compilation modes.","ek9Example":"defines module qa.debugging.compilerphases\n\n  defines class\n\n    Shape as abstract\n      name <- String()\n\n      Shape()\n        -> shapeName as String\n        this.name :=: shapeName\n\n      area() as pure abstract\n        <- rtn as Float?\n\n      operator $ as pure\n        <- rtn as String: `${name}: area=${area()}`\n\n      override operator ? as pure\n        <- rtn as Boolean: name?\n\n    Circle extends Shape\n      radius <- Float()\n\n      Circle()\n        -> r as Float\n        super(\"Circle\")\n        this.radius :=: r\n\n      override area() as pure\n        <- rtn as Float: 3.14159 * radius * radius\n\n      override operator ? as pure\n        <- rtn as Boolean: radius?\n\n    Rectangle extends Shape\n      width <- Float()\n      height <- Float()\n\n      Rectangle()\n        ->\n          w as Float\n          h as Float\n        super(\"Rectangle\")\n        this.width :=: w\n        this.height :=: h\n\n      override area() as pure\n        <- rtn as Float: width * height\n\n      override operator ? as pure\n        <- rtn as Boolean: width? and height?\n\n  defines function\n\n    describeShape() as pure\n      -> shape as Shape\n      <- rtn as String: `${shape}`\n\n  defines program\n\n    CompilerPhasesDemo()\n      stdout <- Stdout()\n\n      // Class hierarchy (validated in phase 6)\n      circle <- Circle(5.0)\n      rect <- Rectangle(3.0, 4.0)\n\n      // Method dispatch (resolved in phase 7)\n      stdout.println(describeShape(circle))\n      stdout.println(describeShape(rect))\n\n      // Flow analysis (checked in phase 9)\n      shapes <- List() of Shape\n      shapes += circle\n      shapes += rect\n\n      for shape in shapes\n        stdout.println(`Shape: ${shape}`)\n\n      // Guard pattern (flow analysis phase 9)\n      parsed <- Float(\"2.5\")\n      if parsed?\n        result <- parsed * parsed\n        stdout.println(`Squared: ${result}`)\n\n      // Try/catch (phase 9 validates exception flow)\n      try\n        stdout.println(\"Compilation phases demo complete\")\n      catch\n        -> ex as Exception\n        stdout.println(`Error: ${ex.reason()}`)","migrationContext":"Java: javac is essentially single-pass with some deferred resolution, no phase control. Python: parsing then bytecode compilation, no intermediate phases exposed. Rust: multi-phase (parsing, name resolution, type checking, borrow checking, MIR, LLVM IR), some phase control with -Z flags. Go: fast single-pass compilation, no phase control. C++: preprocessing, compilation, assembly, linking as separate tools. Kotlin: multi-phase similar to Java, no phase control exposed. EK9: explicit 22-phase pipeline, phase-level control with -Cp flag, LSP stops at phase 9 for fast feedback, clear separation of concerns across phases.","keywords":["analysis","backend","compilation","compile","compiler","debug","error-message","flow","frontend","generation","ir","multi-phase","parsing","phase","pipeline","resolution","symbol","troubleshoot"],"primaryTopics":[],"typicalErrors":[{"error":"E50060","correct":"stdout.println(describeShape(circle))","incorrect":"stdout.println(circle.describe())","explanation":"The method 'describe()' does not exist on Circle. Use the standalone function 'describeShape()' which accepts a Shape parameter. See ek9 -h E50060 for details."}],"companions":[]}
{"id":254,"category":"Advanced Type System","question":"What is the Any type and when should I use it?","url":"https://ek9.io/qa/QA0254.html","alternatePhrasings":["How does the universal base type work in EK9?","What is the root of the EK9 type hierarchy?","When should I use Any instead of a specific type?","How does Any relate to dispatchers?"],"answer":"Any is the universal base interface in EK9. Every type implicitly inherits from Any, making it the root of the entire type hierarchy. This is similar to Java's Object or Python's object, but Any is an interface, not a class.\n\nDEFAULT OPERATORS FROM ANY\nAll types inherit six default operators from Any:\n  ? (isSet) - check if object has a meaningful value\n  == (equality) - compare two objects for equality\n  <=> (comparison) - three-way comparison for ordering\n  $ (string) - convert to human-readable String representation\n  $$ (json) - convert to JSON representation\n  #? (hashcode) - compute a hash code for the object\nThese operators are always available on every type and can be overridden with 'override operator' in your own types.\n\nANY IN THE DISPATCHER PATTERN\nThe most common use of Any is as the fallback parameter in a dispatcher method. The dispatcher entry point takes an Any parameter, and specific overloads handle known types:\n  describe() as dispatcher\n    -> item as Any\n    <- rtn as String: \"Unknown\"\n  private describe()\n    -> item as Integer\n    <- rtn as String: \"Integer\"\nThe Any overload catches everything not handled by specific overloads.\n\nCOST-BASED MATCHING WITH ANY\nWhen the compiler resolves which dispatcher overload to call, Any matches score HIGH_COST (20.0). This means Any is always the LEAST preferred match. Exact type matches score 0.0, superclass matches score 0.05 per level, trait matches score 0.10 per level, and promotion matches score 0.5. Any's 20.0 cost ensures specific overloads always win.\n\nWHEN TO USE ANY\n  Dispatcher entry points - the base method that catches unhandled types\n  Generic programming - when you truly need to work with any type\n  Polymorphic containers - when mixing types is genuinely required\n  Framework-level code - infrastructure that must handle arbitrary types\n\nWHEN NOT TO USE ANY\n  Specific type handling - use the actual type for type safety\n  API parameters - use specific types so callers know what to pass\n  Return types - specific return types help callers avoid type checks\n  When you know the type - Any loses compile-time type information\n\nUsing Any too broadly is a code smell. If you find yourself using Any everywhere and then dispatching on types, consider whether your design could use traits or abstract classes to express the relationship directly.\n\nSee Q24 for the dispatcher pattern and type conversion. See Q211 for double dispatch. See Q238 for the finite operator set. See Q255 for method resolution costs. See Q267 for anti-patterns including overusing Any.","ek9Example":"defines module qa.advancedtypes.anytype\n\n  defines class\n\n    TypeDescriber\n\n      describe() as dispatcher\n        -> item as Any\n        <- rtn as String: \"Unknown type\"\n\n      private describe()\n        -> item as Integer\n        <- rtn as String: `Integer: ${item}`\n\n      private describe()\n        -> item as Float\n        <- rtn as String: `Float: ${item}`\n\n      private describe()\n        -> item as String\n        <- rtn as String: `String: ${item}`\n\n      private describe()\n        -> item as Boolean\n        <- rtn as String: `Boolean: ${item}`\n\n  defines program\n\n    AnyTypeDemo()\n      stdout <- Stdout()\n\n      describer <- TypeDescriber()\n\n      // Heterogeneous list creates List of Any\n      items <- [42, 3.14, \"hello\", true]\n      for listItem in items\n        stdout.println(describer.describe(listItem))\n\n      // Any fallback for types without specific overloads\n      mixedItems <- [Date(), Duration()]\n      for mixedItem in mixedItems\n        stdout.println(describer.describe(mixedItem))\n\n      // Default operators available on every type via Any\n      greeting <- \"Hello\"\n      stdout.println(`isSet: ${greeting?}`)\n      stdout.println(`string: ${greeting}`)\n      stdout.println(`hashcode: ${#?greeting}`)","migrationContext":"Java: Object is the root class, all classes extend Object, has equals/hashCode/toString. Python: object is the root, all classes inherit from it, has __eq__/__hash__/__str__. C#: object/System.Object is universal base, has Equals/GetHashCode/ToString. Rust: no universal base type, uses trait objects (dyn Any) for type erasure. Go: interface{} (or 'any' in Go 1.18+) is the empty interface, no methods guaranteed. Kotlin: Any is the root type with equals/hashCode/toString. EK9: Any is a universal base interface (not class) with six default operators (?, ==, <=>, $, $$, #?), HIGH_COST (20.0) matching ensures specific types always preferred in dispatchers.","keywords":["advanced","any","base","cost","default","dispatcher","fallback","hierarchy","inherit","interface","migrate","object","operator","root","type","type-system","universal"],"primaryTopics":["any type","top type"],"typicalErrors":[{"error":"E50060","correct":"stdout.println(describer.describe(listItem))","incorrect":"stdout.println(describer.process(listItem))","explanation":"TypeDescriber does not have a process() method. The correct method name is describe(). Using a non-existent method triggers E50060 — method not resolved. See ek9 -h E50060 for details."}],"companions":[]}
{"id":255,"category":"Advanced Type System","question":"How does method resolution work with overloading?","url":"https://ek9.io/qa/QA0255.html","alternatePhrasings":["How does EK9 pick which overloaded method to call?","What is cost-based method matching in EK9?","How does the compiler resolve ambiguous method calls?","What are the cost levels in EK9 method resolution?"],"answer":"EK9 uses a cost-based matching algorithm to resolve which overloaded method to call. Each potential match is assigned a numeric cost, and the method with the lowest total cost wins.\n\nFIVE COST LEVELS\nThe compiler assigns costs based on how well each argument matches each parameter:\n  ZERO_COST (0.0) - exact type match, always preferred\n  SUPER_COST (0.05) - superclass match, 0.05 per inheritance level\n  TRAIT_COST (0.10) - trait match, 0.10 per trait level\n  COERCION_COST (0.5) - type promotion via #^ operator\n  HIGH_COST (20.0) - Any type match, universal fallback\n\nHOW MATCHING WORKS\nFor each candidate method, the compiler sums the costs of all parameter matches. The match percentage is calculated as 100.0 minus totalCost. Higher percentage means better match. The method with the highest percentage (lowest cost) wins.\n\nExamples:\n  Exact Integer match: cost 0.0, percentage 100.0%\n  Superclass one level: cost 0.05, percentage 99.95%\n  Trait match: cost 0.10, percentage 99.90%\n  Promoted Integer to Float: cost 0.5, percentage 99.5%\n  Any fallback: cost 20.0, percentage 80.0%\n\nAMBIGUITY DETECTION\nIf two methods score within 0.001 of each other, the compiler reports an ambiguity error rather than guessing. This prevents subtle bugs where adding a new overload silently changes which method gets called.\n\nDISPATCHER RESOLUTION ORDER\nIn a dispatcher, this cost system determines which overload handles each type:\n  1. Exact type match scores 100% - always called first\n  2. Parent class match scores near 100% - called for subtype\n  3. Trait match scores near 100% - called for implementor\n  4. Promotion match scores 99.5% - called after type widening\n  5. Any fallback scores 80% - called only when nothing else matches\n\nMULTI-PARAMETER COSTS\nWith multiple parameters, costs are summed. A method with two exact matches (cost 0.0 + 0.0 = 0.0) beats a method with one exact and one promoted match (cost 0.0 + 0.5 = 0.5).\n\nSee Q24 for the dispatcher pattern. See Q25 for the promote operator and coercion cost. See Q254 for Any type and HIGH_COST. See Q60 for function dispatching. See Q258 for type coercion details.","ek9Example":"defines module qa.advancedtypes.methodresolution\n\n  defines class\n\n    // Three-level hierarchy for demonstrating SUPER_COST\n    Shape as open\n      default Shape() as pure\n\n      override operator ? as pure\n        <- rtn <- true\n\n    Circle extends Shape as open\n      default Circle() as pure\n\n    SmallCircle extends Circle\n      default SmallCircle() as pure\n\n    // Class with two differently-named methods — no overloading\n    Renderer\n\n      // render accepts (Shape, Circle)\n      render()\n        ->\n          first as Shape\n          second as Circle\n        require first? and second?\n\n      // handleShape accepts (Circle, Shape) — different name, no ambiguity\n      handleShape()\n        ->\n          first as Circle\n          second as Shape\n        require first? and second?\n\n  defines program\n\n    MethodResolutionDemo()\n      stdout <- Stdout()\n\n      shape <- Shape()\n      circle <- Circle()\n      small <- SmallCircle()\n\n      renderer <- Renderer()\n\n      // Exact match: render(Shape, Circle) cost 0.0 + 0.0 = 0.0\n      renderer.render(shape, circle)\n\n      // SmallCircle matches Circle at SUPER_COST: cost 0.0 + 0.05 = 0.05\n      renderer.render(shape, small)\n\n      // handleShape is a separate method — no overload ambiguity\n      // circle matches Circle(0.0), small matches Shape at 0.10\n      renderer.handleShape(circle, small)\n\n      stdout.println(\"Method resolution completed\")","migrationContext":"Java: method overloading resolved at COMPILE time based on declared types, not runtime types. Most specific match wins per JLS rules. C++: overload resolution uses implicit conversion sequences ranked by category. Python: no method overloading, uses functools.singledispatch for single-argument dispatch. Rust: no method overloading at all, uses trait dispatch. Go: no method overloading. Kotlin: compile-time overload resolution similar to Java. Julia: multiple dispatch with specificity rules similar to EK9's cost model. EK9: cost-based RUNTIME resolution, five distinct cost levels, ambiguity detection within 0.001 tolerance, works with dispatchers for runtime type-based dispatch.","keywords":["advanced","ambiguity","coercion","cost","dispatch","handler","matching","method","overload","percentage","promotion","resolution","sealed","super","trait","type","type-system","visitor","zero"],"primaryTopics":[],"typicalErrors":[{"error":"E06140","correct":"handleShape(","incorrect":"render(","explanation":"Renaming handleShape to render creates two crossed overloads: render(Shape,Circle) and render(Circle,Shape). Calling render(circle,small) then costs 0.10 for both overloads: circle to Shape(0.05)+small to Circle(0.05) vs circle to Circle(0)+small to Shape(0.10). Equal costs trigger E50210 METHOD_AMBIGUOUS. See ek9 -h E50210 for details."}],"companions":[]}
{"id":256,"category":"Advanced Type System","question":"What is the Void type in EK9?","url":"https://ek9.io/qa/QA0256.html","alternatePhrasings":["How do functions with no return value work in EK9?","What happens when a function does not declare a return?","Is Void a real type in EK9?"],"answer":"Void represents the concept of no return value in EK9. It is the implicit return type when a function or method has no return declaration (no <- line). You cannot instantiate Void, use it as a variable type, or use it as a generic type parameter.\n\nIMPLICIT VOID\nWhen a function or method omits the return declaration, its return type is Void:\n  greet()\n    -> name as String\n    stdout.println(name)\nThis function returns Void because there is no <- declaration. You cannot assign its result to a variable.\n\nFUNCTIONS WITH RETURN VALUES\nA function with a <- declaration has an explicit return type:\n  doubled()\n    -> n as Integer\n    <- rtn as Integer: n * 2\nThis function returns Integer, not Void.\n\nVOID VS UNSET\nThese are fundamentally different concepts:\n  Void: the concept of a value does not apply at all. The function produces no result.\n  Unset: an object exists but has no meaningful value yet. Integer() is an unset Integer.\nA Void function cannot produce a result. An unset variable can later receive a value.\n\nVOID RESTRICTIONS\n  Cannot instantiate: Void() is not valid\n  Cannot declare variables: v as Void is not valid\n  Cannot use as generic parameter: List of Void is not valid\n  Cannot use in stream pipelines: mapping to Void is not valid\n  Cannot assign result: result <- voidFunction() causes a compile error\n\nVoid's isSet always returns false because there is no value to be set.\n\nWHY VOID EXISTS\nVoid gives the type system a way to represent the absence of a return type. Without Void, the compiler would need special-case handling for functions that return nothing. With Void as a type, the type system is uniform: every function has a return type, and Void is the type that means no result.\n\nSee Q29 for the unset concept and tri-state semantics. See Q49 for defining functions. See Q50 for function return values.","ek9Example":"defines module qa.advancedtypes.voidtype\n\n  defines function\n\n    <?-\n      A function that returns nothing (Void).\n      No <- declaration means Void return type.\n    -?>\n    greet()\n      -> name as String\n      stdout <- Stdout()\n      stdout.println(`Hello, ${name}!`)\n\n    <?-\n      A function with an explicit return value.\n      The <- declaration specifies the return type.\n    -?>\n    doubled() as pure\n      -> n as Integer\n      <- rtn as Integer: n * 2\n\n    <?-\n      A pure function that formats a greeting.\n    -?>\n    formatGreeting() as pure\n      -> name as String\n      <- rtn as String: `Welcome, ${name}`\n\n  defines program\n\n    VoidTypeDemo()\n      stdout <- Stdout()\n\n      // Void function: just performs an action, no result\n      greet(\"Steve\")\n\n      // Non-void function: returns a value\n      result <- doubled(21)\n      stdout.println(`Doubled: ${result}`)\n\n      // Can use return value in expressions\n      greeting <- formatGreeting(\"Alice\")\n      stdout.println(greeting)\n\n      // The following would NOT compile:\n      // badResult <- greet(\"Bob\")\n      // Error: cannot assign Void result to a variable","migrationContext":"Java: void keyword (lowercase, not a type), Void class exists but rarely used directly. C#: void keyword, similar to Java. Python: functions without return implicitly return None (which IS a value). Rust: unit type () serves as void, IS a real type with one value. Go: functions without return type return nothing, no void keyword. Kotlin: Unit type (like Rust's unit), is a real singleton value. Swift: Void is a typealias for empty tuple (). EK9: Void is the implicit return type for functions without <- declaration, cannot be instantiated or used as variable type, distinct from unset.","keywords":["advanced","declare","error","function","guard","implicit","method","ok","restriction","result","return","type","type-system","unset","void"],"primaryTopics":["void","void type","no return type"],"typicalErrors":[{"error":"E50060","correct":"stdout.println(`Doubled: ${result}`)","incorrect":"stdout.display(`Doubled: ${result}`)","explanation":"Stdout does not have a display() method. The correct method is println(). Calling a non-existent method triggers E50060 — method not resolved. See ek9 -h E50060 for details."}],"companions":[]}
{"id":257,"category":"Advanced Type System","question":"How do constrained types work in EK9?","url":"https://ek9.io/qa/QA0257.html","alternatePhrasings":["What is the difference between constrain as and constrain by?","How do I create a type with validation constraints?","How do I restrict the values a type can hold?","What is a constrained type in EK9?"],"answer":"EK9 has two distinct constraint mechanisms with different semantics: value constraints that create disconnected types, and generic bounds that restrict type parameters.\n\nVALUE CONSTRAINTS WITH CONSTRAIN AS\nThe 'constrain as' syntax creates a NEW type constrained by a pattern or condition:\n  Name as String constrain as\n    matches /^[a-zA-Z -]+$/\nName is a new type that validates its content at construction time. A bare constructor ASSERTS validity: it panics at runtime on a set invalid value (and a literal constant that provably violates is the compile error E08260). Use the fallible '.of(value)' factory for untrusted input — it returns an unset value on failure instead of panicking. Name shares operators with String (you can compare, convert to string, etc.) but it is NOT a String subtype. You cannot pass a Name where a String is expected. This is a LIKE-A relationship, not an IS-A relationship.\n\nRANGE CONSTRAINTS WITH CONSTRAIN\nThe 'constrain' syntax restricts numeric values to a range:\n  PositiveIndex as Integer constrain\n    > 0\nPositiveIndex only accepts values greater than zero. Like 'constrain as', this creates a disconnected type.\n\nGENERIC BOUNDS WITH CONSTRAIN BY\nThe 'constrain by' syntax restricts generic type parameters:\n  Handler of type T constrain by Comparable\nThis preserves IS-A relationships: T must be a subtype of the bound. Inside the generic, you can call methods from the constraining type.\n\nWHY DISCONNECTED TYPES?\nValue-constrained types are deliberately disconnected from their base type. If EmailAddress extended String, you could pass an EmailAddress anywhere a String is expected, bypassing validation. By making them disconnected, the compiler prevents accidental substitution:\n  processEmail()\n    -> addr as EmailAddress\n    // Only accepts validated emails\n  processString()\n    -> text as String\n    // EmailAddress cannot be passed here\nThis forces explicit conversion when crossing type boundaries.\n\nCONSTRAINT VALIDATION\nConstraints are checked at construction time. A bare constructor ASSERTS validity and panics at runtime if a set value violates the constraint (a violating literal constant is the compile error E08260). For untrusted input use the fallible '.of(value)' factory, which returns an unset value on failure instead of panicking:\n  badName <- Name().of(\"123\")\n  if badName?\n    // Will not enter: constraint failed\nUse the ? operator to check if construction succeeded.\n\nSee Q195 for generic type constraints with constrain by. See Q223 for constrained enumerations. See Q27 for static typing benefits. See Q269 for input validation with constrained types.","ek9Example":"defines module qa.advancedtypes.constrainedtypes\n\n  defines class\n\n    Person\n      firstName as String?\n      surname as String?\n\n      default private Person() as pure\n\n      Person() as pure\n        ->\n          first as String\n          last as String\n        firstName :=? String(first)\n        surname :=? String(last)\n\n      // Copy constructor: a constrained type (ValidPerson below) stores an INDEPENDENT copy of its\n      // base, so the base needs a copy mechanism — either this public copy constructor Person(Person)\n      // or a public no-arg constructor plus the ':=:' operator. Without one, ValidPerson is rejected\n      // with E06131. Person keeps its no-arg constructor private, so the copy constructor is the fit.\n      Person() as pure\n        -> from as Person\n        firstName :=? String(from.firstName)\n        surname :=? String(from.surname)\n\n      operator matches as pure\n        -> pattern as RegEx\n        <- rtn as Boolean: $this matches pattern\n\n      operator $ as pure\n        <- rtn as String: `${firstName} ${surname}`\n\n      override operator ? as pure\n        <- rtn as Boolean: firstName? and surname?\n\n  defines type\n\n    // Value constraint: Name must match letters and spaces only\n    Name as String constrain as\n      matches /^[a-zA-Z -]+$/\n\n    // Range constraint: PositiveIndex must be greater than zero\n    PositiveIndex as Integer constrain\n      > 0\n\n    // User-defined type constraint: Person must have first and last name\n    ValidPerson as Person constrain as\n      matches /^[a-zA-Z]+ [a-zA-Z]+$/\n\n  defines program\n\n    ConstrainedTypesDemo()\n      stdout <- Stdout()\n\n      // === VALUE CONSTRAINT: Name ===\n\n      // Valid construction - fallible factory returns a set value\n      goodName <- Name().of(\"Alice Smith\")\n      stdout.println(`Valid name set: ${goodName?}`)\n\n      if goodName?\n        stdout.println(`Name: ${goodName}`)\n\n      // Invalid construction - fallible factory returns an unset value\n      badName <- Name().of(\"123!@#\")\n      stdout.println(`Invalid name set: ${badName?}`)\n\n      // === RANGE CONSTRAINT: PositiveIndex ===\n\n      // Valid: greater than zero\n      goodIndex <- PositiveIndex().of(5)\n      stdout.println(`Valid index set: ${goodIndex?}`)\n\n      if goodIndex?\n        stdout.println(`Index: ${goodIndex}`)\n\n      // Invalid: zero is not > 0 - fallible factory returns unset\n      badIndex <- PositiveIndex().of(0)\n      stdout.println(`Zero index set: ${badIndex?}`)\n\n      // Invalid: negative - fallible factory returns unset\n      negIndex <- PositiveIndex().of(-3)\n      stdout.println(`Negative index set: ${negIndex?}`)\n\n      // === USER-DEFINED TYPE CONSTRAINT: ValidPerson ===\n\n      // Person with operator matches enables constraining\n      goodPerson <- ValidPerson().of(Person(\"Alice\", \"Smith\"))\n      stdout.println(`Valid person set: ${goodPerson?}`)\n\n      if goodPerson?\n        stdout.println(`Person: ${goodPerson}`)\n\n      // Person with numbers in name fails the regex constraint - fallible factory returns unset\n      badPerson <- ValidPerson().of(Person(\"123\", \"456\"))\n      stdout.println(`Bad person set: ${badPerson?}`)\n\n      // === GUARDED ASSIGNMENT WITH CONSTRAINTS ===\n\n      // Guard pattern works naturally with constrained types\n      if validName <- Name().of(\"Bob Jones\")\n        stdout.println(`Guarded name: ${validName}`)\n\n      if invalidName <- Name().of(\"\")\n        stdout.println(\"Should not print\")","migrationContext":"Java: no built-in value constraints, uses Bean Validation annotations (@Pattern, @Min). Python: no type-level constraints, runtime checks with validators. Rust: no constrained types, uses newtype pattern with manual validation. Go: no type constraints, runtime validation. Kotlin: value classes with init validation, but still subtypes. Swift: no constrained types, uses property wrappers. C#: no type-level constraints, uses data annotations. EK9: built-in 'constrain as' for pattern constraints, 'constrain' for range constraints, creates disconnected types (LIKE-A not IS-A), validation at construction time: a bare constructor asserts validity (panics on a set invalid value, or compile error E08260 for a violating literal), while the fallible '.of(value)' factory returns unset on failure for untrusted input.","keywords":["advanced","bound","constant","constrain","constraint","disconnected","generic","like-a","pattern","range","restrict","type","type-system","validate","validation"],"primaryTopics":["constrained type","type constraint","newtype"],"typicalErrors":[{"error":"E50060","correct":"operator matches as pure","incorrect":"doesMatch() as pure","explanation":"Renaming 'operator matches' to method 'doesMatch' means Person no longer has the matches operator. The constrained type ValidPerson uses matches in its constraint expression, but the cloned operator no longer exists on the base type, triggering E50060. Constrained types inherit operators from their base — remove the operator and the constraint breaks. See ek9 -h E50060 for details."}],"companions":[]}
{"id":258,"category":"Advanced Type System","question":"What is type coercion and promotion in method calls?","url":"https://ek9.io/qa/QA0258.html","alternatePhrasings":["How does automatic type promotion work in EK9 method calls?","What happens when I pass an Integer where a Float is expected?","How does the #^ operator affect method resolution?","Why does EK9 only allow one level of type promotion?"],"answer":"Type coercion in EK9 is the automatic widening of a value from one type to a compatible wider type. This happens through the promote operator (#^) and is tightly integrated with the cost-based method resolution system.\n\nHOW PROMOTION WORKS\nWhen you pass an Integer where a Float is expected, the compiler checks if Integer has a #^ operator that returns Float. It does, so the compiler automatically inserts the promotion call. The promoted value is then passed to the function.\n\nBUILT-IN PROMOTIONS\n  Integer to Float - safe numeric widening\n  Character to String - single character to text\n  Date to DateTime - date to full timestamp\n  Millisecond to Duration - time unit widening\n\nPROMOTION IN METHOD RESOLUTION\nPromotion carries a COERCION_COST of 0.5 in the method matching system. This means:\n  Exact match (0.0) always wins over a promoted match (0.5)\n  Superclass match (0.05) wins over promotion\n  Trait match (0.10) wins over promotion\n  Promotion (0.5) wins over Any fallback (20.0)\n\nExample: if a function has overloads for Float and Any, passing an Integer selects the Float overload (cost 0.5) over the Any overload (cost 20.0).\n\nSINGLE PROMOTION ONLY\nThe compiler attempts exactly ONE promotion step. It does NOT chain promotions. If Integer promotes to Float and Float promoted to something else, the compiler would NOT go Integer to Float to that other type. It tries one step and stops.\n\nThis is a deliberate safety measure. Chained promotions in other languages are a major source of bugs:\n  C++: user-defined conversions chain with standard conversions\n  JavaScript: == operator coerces through multiple steps\n  Scala 2: implicit conversions could chain unpredictably\n\nUSER-DEFINED PROMOTION\nDefine #^ on your own types for custom promotion:\n  Measurement\n    operator #^ as pure\n      <- rtn as String: formatted text\nA type can have only ONE #^ operator.\n\nEXPLICIT PROMOTION\nYou can explicitly invoke promotion:\n  intVal <- 42\n  floatVal <- #^ intVal\nThis calls Integer's #^ operator directly.\n\nSee Q24 for type conversion overview. See Q25 for the promote operator in detail. See Q250 for fixing type mismatch errors. See Q255 for the full method resolution algorithm.","ek9Example":"defines module qa.advancedtypes.typecoercion\n\n  defines function\n\n    <?-\n      Accepts a Float parameter.\n      Integer arguments are automatically promoted.\n    -?>\n    processFloat() as pure\n      -> mainValue as Float\n      <- rtn as String: `Float: ${mainValue}`\n\n    <?-\n      Accepts a String parameter.\n      Character arguments are automatically promoted.\n    -?>\n    processString() as pure\n      -> mainValue as String\n      <- rtn as String: `String: ${mainValue}`\n\n  defines class\n\n    Temperature\n      degrees <- Float()\n\n      Temperature()\n        -> initialDegrees as Float\n        degrees :=: initialDegrees\n\n      // User-defined promotion: Temperature to Float\n      operator #^ as pure\n        <- rtn as Float: Float(degrees)\n\n      operator $ as pure\n        <- rtn as String: `${degrees}C`\n\n      default operator ?\n\n  defines program\n\n    TypeCoercionDemo()\n      stdout <- Stdout()\n\n      // === BUILT-IN PROMOTION: Integer to Float ===\n\n      // Integer promoted to Float automatically (cost 0.5)\n      floatResult <- processFloat(42)\n      stdout.println(floatResult)\n\n      // Float passed directly (cost 0.0, exact match)\n      exactResult <- processFloat(3.14)\n      stdout.println(exactResult)\n\n      // === BUILT-IN PROMOTION: Character to String ===\n\n      charResult <- processString('Z')\n      stdout.println(charResult)\n\n      directResult <- processString(\"hello\")\n      stdout.println(directResult)\n\n      // === USER-DEFINED PROMOTION IN CALCULATION ===\n\n      // Temperature promotes to Float via #^ for arithmetic\n      boiling <- Temperature(100.0)\n      freezing <- Temperature(0.0)\n\n      // Automatic promotion: Temperature to Float in function call\n      boilingResult <- processFloat(boiling)\n      stdout.println(boilingResult)\n\n      // Explicit promotion with #^\n      boilingFloat <- #^ boiling\n      freezingFloat <- #^ freezing\n      range <- boilingFloat - freezingFloat\n      stdout.println(`Temperature range: ${range}`)","migrationContext":"Java: autoboxing + widening, Integer == Long compares identity not value, ternary widens unexpectedly. C++: implicit conversion sequences can chain standard + user-defined, explicit keyword to prevent. JavaScript: == coerces through multiple steps, === exists because == is unreliable. Python: mostly explicit, but __add__/NotImplemented/__radd__ chains exist. Rust: no implicit conversion at all, From/Into traits require explicit calls. Go: no implicit conversion, all conversions explicit. Kotlin: no implicit widening, explicit .toFloat() required. EK9: single-level promotion via #^ operator, COERCION_COST (0.5) in method resolution, no chaining, predictable and safe.","keywords":["advanced","automatic","chain","coercion","conversion","cost","implicit","method","migrate","promote","promotion","resolution","single","type","type-system","widening"],"primaryTopics":[],"typicalErrors":[{"error":"E06270","correct":"operator #^ as pure","incorrect":"asFloat() as pure","explanation":"Renaming 'operator #^' to method 'asFloat' removes the promote operator from Temperature. The call processFloat(boiling) relied on automatic promotion from Temperature to Float via #^. Without the operator, the compiler finds processFloat(Float) but cannot match the Temperature argument, triggering E06270 parameter mismatch. For automatic type promotion to work, you MUST define 'operator #^', not a regular method. See ek9 -h E06270 for details."}],"companions":[]}
{"id":259,"category":"Advanced Type System","question":"How does EK9's Result type differ from other languages?","url":"https://ek9.io/qa/QA0259.html","alternatePhrasings":["Why does EK9's Result have four states?","How does Result's four-state model work?","What makes EK9's Result unique compared to Rust's Result?"],"answer":"EK9's Result type has four states instead of the two states found in most languages. This is a deliberate type system design that handles real-world scenarios other languages cannot express.\n\nFOUR STATES NOT TWO\nMost languages (Rust, Swift, Kotlin) treat Result as strictly either success OR error. EK9's Result of (O, E) has four independent states:\n  1. Neither: both ok and error are unset. Represents incomplete or pending.\n  2. Ok only: success value present, no error.\n  3. Error only: error value present, no success.\n  4. Both ok AND error: success AND error both present simultaneously.\n\nWHY FOUR STATES?\nReal-world operations genuinely produce both success and error simultaneously:\n  Configuration lookup fails but returns a default value plus an error code.\n  Data migration converts a record but logs warnings.\n  API returns partial results with error details about missing fields.\nIn these cases, the caller needs BOTH the usable result AND the error information. Rust's Result forces you to choose one or the other.\n\nINDEPENDENT CHECKS\nisOk() and isError() are independent boolean checks. Both can return true:\n  r <- Result(\"Default\", -1)\n  r.isOk()    // true\n  r.isError() // true\nThe ? operator checks isOk (biased toward success).\n\nCOMPILE-TIME GUARD ENFORCEMENT\nThe compiler enforces that you cannot access .ok() without an isOk() guard, and cannot access .error() without an isError() guard. There is no .unwrap() that panics at runtime. No escape hatches.\n  if r.isOk()\n    okData <- r.ok()\n  if r.isError()\n    errData <- r.error()\n\nCONTRAST WITH OTHER LANGUAGES\n  Rust: Result<T,E> is strictly Ok OR Err. .unwrap() panics at runtime. Forces choice between success and error.\n  Go: returns (value, error) tuple. No compiler enforcement. Caller can ignore error. No type safety.\n  Java: no Result type. Uses exceptions which are invisible in method signatures.\n  Swift: Result<Success,Failure> is strictly success OR failure, like Rust.\n  Kotlin: kotlin.Result exists but no compiler-enforced safe access.\n\nEK9's four-state model with compile-time guards is unique. It eliminates the entire class of unwrap panics (Rust), ignored errors (Go), and unchecked exceptions (Java).\n\nSee Q48 for Result basics and creation. See Q86 for advanced guard patterns. See Q87 for Result operations. See Q139 for choosing between try/catch and Result.","ek9Example":"defines module qa.advancedtypes.resulttypesystem\n\n  defines function\n\n    <?-\n      Returns ok only: configuration found.\n    -?>\n    getConfig()\n      <- rtn <- Result(\"production\", Integer())\n\n    <?-\n      Returns error only: lookup failed.\n    -?>\n    getFailedConfig()\n      <- rtn <- Result(String(), 404)\n\n    <?-\n      Returns BOTH ok and error: fallback used with warning.\n    -?>\n    getConfigWithFallback()\n      <- rtn <- Result(\"default-value\", -1)\n\n    <?-\n      Returns neither: operation not yet complete.\n    -?>\n    getPendingConfig()\n      <- rtn <- Result() of (String, Integer)\n\n  defines program\n\n    ResultTypeSystemDemo()\n      stdout <- Stdout()\n\n      // === FOUR STATES ===\n\n      okResult <- getConfig()\n      errorResult <- getFailedConfig()\n      bothResult <- getConfigWithFallback()\n      neitherResult <- getPendingConfig()\n\n      // isOk and isError are independent\n      stdout.println(`Ok result - isOk: ${okResult.isOk()}, isError: ${okResult.isError()}`)\n      stdout.println(`Error result - isOk: ${errorResult.isOk()}, isError: ${errorResult.isError()}`)\n      stdout.println(`Both result - isOk: ${bothResult.isOk()}, isError: ${bothResult.isError()}`)\n      stdout.println(`Neither result - empty: ${neitherResult is empty}`)\n\n      // === COMPILE-TIME GUARDS ===\n\n      // Guard required before accessing .ok()\n      if okResult.isOk()\n        stdout.println(`Config: ${okResult.ok()}`)\n\n      // Guard required before accessing .error()\n      if errorResult.isError()\n        stdout.println(`Error code: ${errorResult.error()}`)\n\n      // AND guard: both checks satisfied, both accesses safe\n      if bothResult.isOk() and bothResult.isError()\n        stdout.println(`Fallback value: ${bothResult.ok()}`)\n        stdout.println(`Warning code: ${bothResult.error()}`)\n\n      // === SAFE DEFAULTS ===\n\n      // No guard needed with defaults\n      safeConfig <- errorResult.okOrDefault(\"fallback\")\n      stdout.println(`Safe config: ${safeConfig}`)\n\n      safeError <- okResult.errorOrDefault(0)\n      stdout.println(`Safe error: ${safeError}`)","migrationContext":"Rust: Result<T,E> is strictly Ok or Err (two states), .unwrap() panics at runtime, ? operator for propagation, forces Either semantics. Go: (value, error) return convention, no compiler enforcement, errors easily ignored, nil error means success. Java: no Result type, checked exceptions in signatures but unchecked exceptions invisible, try-catch is verbose. Swift: Result<Success,Failure> with associated values, strictly two states, get() throws on failure. Kotlin: kotlin.Result wrapping, no compiler-enforced guards, .getOrThrow() can fail. Haskell: Either monad is strictly Left or Right. EK9: Result of (O, E) has FOUR states (neither, ok-only, error-only, BOTH), compile-time guard enforcement, no unwrap, no escape hatches.","keywords":["advanced","both","compile","differ","enforce","error","four","guard","independent","isset","migrate","null-safe","ok","panic","result","safe","safety","state","type","type-system","unique","unwrap"],"primaryTopics":[],"typicalErrors":[{"error":"E08030","correct":"if okResult.isOk()","incorrect":"if okResult.isError()","explanation":"Guarding with isError() does not prove isOk() is true. Accessing .ok() requires an isOk() guard. The compiler tracks which guard was used and rejects mismatched access, triggering E08030. See ek9 -h E08030 for details."},{"error":"E08030","correct":"isOk() and bothResult.isError()","incorrect":"isOk() or bothResult.isError()","explanation":"Changing 'and' to 'or' means neither isOk() nor isError() is individually guaranteed. With 'and', both guards are satisfied so both .ok() and .error() are safe. With 'or', either could be false, so the compiler rejects access to both, triggering E08030. See ek9 -h E08030 for details."}],"companions":[]}
{"id":260,"category":"Advanced Type System","question":"What types can be used as Dict keys?","url":"https://ek9.io/qa/QA0260.html","alternatePhrasings":["What are the requirements for Dict key types?","Can I use custom classes as Dict keys in EK9?","How does Dict find keys internally?","Do Dict keys need hashcode and comparison operators?"],"answer":"Dict keys must support equality and hashing, giving O(1) lookups. Internally, Dict is backed by a Java LinkedHashMap whose key matching uses the key type's '==' (equality) and '#?' (hashcode) operators — the compiler bridges these to Java equals()/hashCode() for generated user types. Two keys match when '==' returns true and they share the same '#?' hash. The '<=>' (comparison) operator is NOT used for Dict key lookup at runtime, but it IS still required for the 'default operator' set (default '==' depends on '<=>' being defaulted first).\n\nKEY REQUIREMENTS\nA type used as a Dict key must implement:\n  operator == (equality) - used for Dict key matching at runtime; required\n  operator #? (hashcode) - used for Dict O(1) hashing at runtime; required\n  operator <=> (comparison) - prerequisite for the 'default operator' set and for sorting; not used for Dict lookup itself, returns Integer\n  operator $ (string) - for string representation\n  operator ? (isSet) - for validity checking\n\nBUILT-IN KEY TYPES\nAll these types work as Dict keys out of the box:\n  String, Integer, Float, Character, Boolean\n  Date, DateTime, Duration, Millisecond\n  Enumerations (all enum values support comparison)\n\nCUSTOM KEY TYPES\nUse records with default operators for the simplest approach:\n  PlayerName\n    firstName as String: String()\n    lastName as String: String()\n    default PlayerName()\n    PlayerName() ...\n    default operator <=>\n    default operator #?\n    default operator ==\n    default operator $\n    default operator ?\nThe 'default' keyword auto-generates these operators based on field declarations.\n\nYou can also implement operators manually for custom comparison logic.\n\nDICT INTERNALS\nDict uses LinkedHashMap internally, which means insertion order is preserved. When you iterate over a Dict, entries come out in the order they were added. Key equality is determined by the '==' operator (bridged to the generated equals()/hashCode()), and keys are hashed via '#?', giving O(1) contains/getOrDefault/remove via the native LinkedHashMap rather than a linear scan.\n\nENUMERATION KEYS\nEnumerations make excellent Dict keys because they have built-in comparison and hashcode:\n  scores <- Dict() of (Direction, Integer)\n  scores += DictEntry(Direction.North, 10)\n\nSee Q46 for Dict basics. See Q90 for Dict operations. See Q129 for handling missing Dict keys. See Q167 for Dict iteration patterns.","ek9Example":"defines module qa.advancedtypes.dictkeytype\n\n  defines record\n\n    PlayerName\n      firstName as String: String()\n      lastName as String: String()\n\n      default PlayerName()\n\n      PlayerName()\n        ->\n          fn as String\n          ln as String\n        firstName: fn\n        lastName: ln\n\n      default operator <=>\n      default operator #?\n      default operator ==\n      default operator $\n      default operator ?\n\n  defines program\n\n    DictKeyTypesDemo()\n      stdout <- Stdout()\n\n      // === BUILT-IN TYPES AS KEYS ===\n\n      // String keys (most common)\n      ages <- {\"Alice\": 30, \"Bob\": 25}\n      stdout.println(`String keys: ${ages}`)\n\n      // Integer keys\n      labels <- {1: \"first\", 2: \"second\", 3: \"third\"}\n      stdout.println(`Integer keys: ${labels}`)\n\n      // === CUSTOM RECORD AS KEY ===\n\n      scores <- Dict() of (PlayerName, Integer)\n      scores += DictEntry(PlayerName(\"Alice\", \"Smith\"), 95)\n      scores += DictEntry(PlayerName(\"Bob\", \"Jones\"), 87)\n      scores += DictEntry(PlayerName(\"Charlie\", \"Brown\"), 72)\n\n      // Lookup by key (matched via == and #? hashing)\n      aliceScore <- scores.getOrDefault(PlayerName(\"Alice\", \"Smith\"), 0)\n      stdout.println(`Alice score: ${aliceScore}`)\n\n      // Missing key returns default\n      unknownScore <- scores.getOrDefault(PlayerName(\"Dave\", \"Wilson\"), 0)\n      stdout.println(`Unknown score: ${unknownScore}`)\n\n      // === ITERATION PRESERVES INSERTION ORDER ===\n\n      for entry in scores\n        stdout.println(`${entry.key()}: ${entry.value()}`)\n\n      // === LENGTH AND EMPTY CHECK ===\n\n      stdout.println(`Score count: ${length scores}`)\n      stdout.println(`Scores empty: ${scores is empty}`)","migrationContext":"Java: HashMap requires equals() and hashCode() on keys, LinkedHashMap preserves insertion order. Python: dict keys must be hashable (__hash__ and __eq__). Rust: HashMap keys need Hash + Eq traits. Go: map keys must be comparable (==). C#: Dictionary keys need GetHashCode() and Equals(). Kotlin: same as Java (equals/hashCode). EK9: Dict keys match by '==' (equality) and '#?' (hashcode) via a backing LinkedHashMap (insertion order preserved); records use 'default operator' to auto-generate the full set, which also includes '<=>' (still the default-operator prerequisite).","keywords":["advanced","comparison","custom","dict","enumeration","hashcode","insertion","key","operator","order","record","requirement","type","type-system"],"primaryTopics":[],"typicalErrors":[{"error":"E07180","correct":"default operator <=>","incorrect":"//removed compare","explanation":"Removing 'default operator <=>' from PlayerName means the record has no comparison operator. This cascades: 'default operator ==' depends on '<=>' and fails with E07180. Without comparison and equality, the type cannot function as a Dict key. The <=> operator is foundational — other operators depend on it and Dict key lookup requires it. See ek9 -h E07180 for details."}],"companions":[]}
{"id":261,"category":"Design Patterns and Idioms","question":"What is idiomatic EK9 code?","url":"https://ek9.io/qa/QA0261.html","alternatePhrasings":["How do I write good EK9 code?","What are EK9 best practices and conventions?","What is the EK9 way of doing things?"],"answer":"Idiomatic EK9 uses the language's built-in features to write concise, safe, and expressive code. Four key idiom categories:\n\nCONCISENESS: Backtick interpolation (Q113). Coalescing: ??, ?:, <?, >? (Q243). Guarded assignment :=? (Q74-Q78). Natural language operators: contains, matches (Q171). Control flow as expressions (Q68, Q80-Q83).\n\nOPERATORS: Fixed set of ~50 operators (Q238). Default operators auto-generate ==, #?, $ (Q102). Pure vs mutation: + creates new, += mutates (Q241). Copy/merge/replace: :=:, :~:, :^: (Q87, Q90).\n\nSAFETY: Purity via as pure (Q54). Universal guards (Q74-Q78). Tri-state: absent/unset/set, no null (Q29). No break/continue/return (Q148). Constrained types (Q257).\n\nCOMPOSITION: Closed types by default (Q102). Trait delegation with by (Q210). Dynamic functions/classes (Q115). Stream pipelines (Q235-Q237). Dispatcher pattern (Q211).\n\nSee Q29, Q48, Q54, Q55, Q68, Q69, Q74, Q80, Q87, Q90, Q102, Q113, Q115, Q148, Q171, Q210, Q211, Q215, Q235-Q238, Q241, Q243, Q254, Q257-Q259, Q268 for details on each pattern.","ek9Example":"defines module qa.patterns.idiomatic\n\n  defines function\n\n    isNonEmpty() as pure\n      -> item as String\n      <- rtn as Boolean: item?\n\n  defines program\n\n    IdiomaticEk9Demo()\n      stdout <- Stdout()\n\n      // === GUARD EXPRESSION ===\n      // Block only executes if the value is set\n\n      greeting <- String(\"Hello EK9\")\n      if msg <- greeting\n        stdout.println(msg)\n\n      // === STRING INTERPOLATION ===\n\n      lang <- \"EK9\"\n      stdout.println(`Welcome to ${lang}`)\n\n      // === COALESCING FOR DEFAULTS ===\n\n      label <- String()\n      displayLabel <- label ?: \"default\"\n      stdout.println(`Label: ${displayLabel}`)\n\n      // === GUARDED ASSIGNMENT ===\n      // First-wins pattern\n\n      config <- String()\n      config :=? \"first-wins\"\n      config :=? \"ignored\"\n      stdout.println(`Config: ${config}`)\n\n      // === STREAM PIPELINE ===\n\n      words <- [\"hello\", \"\", \"ek9\", \"\", \"code\"]\n      nonEmpty <- cat words | filter by isNonEmpty | collect as List of String\n      stdout.println(`Non-empty words: ${length nonEmpty}`)\n\n      // === NATURAL LANGUAGE OPERATOR ===\n\n      text <- \"Hello World\"\n      if text contains \"World\"\n        stdout.println(\"Found World in text\")\n\n      stdout.println(\"Idiomatic EK9 in action\")","migrationContext":"Java: verbose, null-heavy, mutable/open by default. Python: dynamic typing, no compile-time safety. Rust: ownership complexity, similar Result pattern. EK9: combines safety (tri-state, guards, purity), expressiveness (operators, streams), and architecture (closed types, delegation) into a coherent idiom set.","keywords":["best","convention","design","good","idiom","idiomatic","overview","pattern","philosophy","practice","proper","right","style","way","write"],"primaryTopics":[],"typicalErrors":[{"error":"E50001","correct":"greeting <- String(\"Hello EK9\")","incorrect":"greetingXYZ <- String(\"Hello EK9\")","explanation":"Renaming the variable means later references to 'greeting' become unresolved, triggering E50001 — not resolved. Variable names must be consistent. See ek9 -h E50001 for details."},{"error":"E50001","correct":"lang <- \"EK9\"","incorrect":"langXYZ <- \"EK9\"","explanation":"Renaming the variable means later references to 'lang' become unresolved, triggering E50001 — not resolved. Variable names must be consistent. See ek9 -h E50001 for details."}],"companions":[]}
{"id":262,"category":"Design Patterns and Idioms","question":"How do I implement the observer pattern in EK9?","url":"https://ek9.io/qa/QA0262.html","alternatePhrasings":["How do I use events and listeners in EK9?","How do I implement publish-subscribe in EK9?","How do function delegates work as event listeners?"],"answer":"EK9 implements the observer pattern using abstract functions as listener types and lists of function delegates as subscriber collections. This avoids the interface boilerplate of Java's Observer/Observable.\n\nABSTRACT FUNCTION AS LISTENER\nDefine the listener contract as an abstract function:\n  Listener() as abstract\n    -> event as String\nThis is the subscription interface. Any function matching this signature can be a listener.\n\nEVENT EMITTER CLASS\nStore listeners in a list and iterate to notify:\n  EventEmitter\n    listeners as List of Listener\n    subscribe()\n      -> listener as Listener\n      listeners += listener\n    emit()\n      -> event as String\n      for listener in listeners\n        listener(event)\n\nDYNAMIC FUNCTION SUBSCRIBERS\nCreate inline listeners with dynamic functions:\n  printListener <- () is Listener as function\n    Stdout().println(event)\nThe event parameter comes from the Listener abstract function signature.\n\nCAPTURE FOR CONTEXT\nUse explicit capture to add context to listeners:\n  prefix <- \"[AUDIT]\"\n  auditListener <- (prefix) is Listener as function\n    Stdout().println(prefix + \": \" + event)\nThe prefix is captured by value at creation time.\n\nADDING LISTENERS\nSubscribe without modifying the emitter:\n  emitter.subscribe(printListener)\n  emitter.subscribe(auditListener)\n  emitter.emit(\"user-login\")\nNew listeners can be added at any time.\n\nCONSUMER VS ACCEPTOR\nListeners that only read are consumers (pure). Listeners that cause side effects (printing, logging) are acceptors (impure). The abstract function's purity annotation controls this.\n\nSee Q54 for consumer and acceptor function types. See Q55 for function delegates. See Q115 for dynamic classes with capture. See Q214 for strategy pattern using similar function callbacks.","ek9Example":"defines module qa.patterns.observer\n\n  defines function\n\n    Listener() as abstract\n      -> event as String\n\n  defines class\n\n    EventEmitter\n      listeners as List of Listener: List() of Listener\n\n      subscribe()\n        -> listener as Listener\n        listeners += listener\n\n      emit()\n        -> event as String\n        for listener in listeners\n          listener(event)\n\n      subscriberCount() as pure\n        <- rtn as Integer: length listeners\n\n      default operator ?\n\n  defines program\n\n    ObserverPatternDemo()\n      stdout <- Stdout()\n\n      emitter <- EventEmitter()\n\n      // === DYNAMIC FUNCTION LISTENERS ===\n\n      printListener <- () is Listener as function\n        Stdout().println(`Event received: ${event}`)\n\n      // Listener with captured context\n      prefix <- \"[AUDIT]\"\n      auditListener <- (prefix) is Listener as function\n        Stdout().println(`${prefix} ${event}`)\n\n      // === SUBSCRIBE ===\n\n      emitter.subscribe(printListener)\n      emitter.subscribe(auditListener)\n\n      stdout.println(`Subscribers: ${emitter.subscriberCount()}`)\n\n      // === EMIT EVENTS ===\n\n      emitter.emit(\"user-login\")\n      emitter.emit(\"data-saved\")\n\n      // === ADD MORE LISTENERS LATER ===\n\n      countListener <- () is Listener as function\n        Stdout().println(`Counted: ${event}`)\n\n      emitter.subscribe(countListener)\n      emitter.emit(\"final-event\")","migrationContext":"Java: Observer/Observable (deprecated), listeners via interfaces, anonymous inner classes, lambda expressions since Java 8, EventListener pattern. Python: no built-in observer, callback functions or third-party libraries. JavaScript: EventEmitter (Node.js), addEventListener (DOM), callback-heavy. Rust: no built-in observer, channels or callback closures. Go: channels for pub/sub, callback functions. EK9: abstract functions as listener types, List of Listener for subscriber management, dynamic functions with capture for inline listeners.","keywords":["callback","delegate","design","emit","event","idiom","listener","migrate","notify","observer","pattern","publish","subscribe"],"primaryTopics":["observer pattern","event listener"],"typicalErrors":[{"error":"E08180","correct":"listeners as List of Listener: List() of Listener","incorrect":"listeners as List of Listener","explanation":"Class fields must be initialized inline. Declaring a field without an initializer triggers E08180. Always provide an initializer, such as 'List() of Listener' for an empty list. See ek9 -h E08180 for details."},{"error":"E50001","correct":"emitter <- EventEmitter()","incorrect":"emitterXYZ <- EventEmitter()","explanation":"Renaming the variable means later references to 'emitter' become unresolved, triggering E50001. Variable names must be consistent throughout the scope. See ek9 -h E50001 for details."}],"companions":[]}
{"id":263,"category":"Design Patterns and Idioms","question":"How do I implement the factory pattern in EK9?","url":"https://ek9.io/qa/QA0263.html","alternatePhrasings":["How do I create objects via a factory function in EK9?","How do I return different implementations from a function?","How do I use switch to select implementations in EK9?"],"answer":"EK9 implements the factory pattern using functions that return trait implementations. The factory uses switch/case to select the appropriate concrete class based on input parameters.\n\nTRAIT AS PRODUCT INTERFACE\nDefine a trait as the product contract:\n  Logger\n    log() as abstract\n      -> msg as String\n      <- rtn as String?\nAll factory products implement this trait.\n\nCONCRETE IMPLEMENTATIONS\nCreate classes that implement the trait:\n  ConsoleLogger with trait of Logger\n    override log()\n      -> msg as String\n      <- rtn as String: \"[CONSOLE] \" + msg\n  PrefixLogger with trait of Logger\n    override log()\n      -> msg as String\n      <- rtn as String: \"[PREFIX] \" + msg\n\nFACTORY FUNCTION WITH SWITCH\nThe factory selects the implementation:\n  createLogger()\n    -> logType as String\n    <- rtn as Logger?\n    switch logType\n      case \"console\"\n        rtn: ConsoleLogger()\n      default\n        rtn: ConsoleLogger()\n\nDYNAMIC CLASS FACTORY\nFor lightweight one-off implementations, create dynamic classes inside the factory:\n  customLogger <- () with trait of Logger as class\n    override log()\n      -> msg as String\n      <- rtn as String: \"[CUSTOM] \" + msg\n\nWHEN TO USE FACTORY\nUse factories when the caller should not know which concrete class is created. The factory encapsulates the creation logic and returns the trait type.\n\nSee Q103 for abstract classes as alternatives to traits. See Q115 for dynamic classes in factories. See Q211 for dispatcher as an alternative to factory switch logic.","ek9Example":"defines module qa.patterns.factory\n\n  defines trait\n\n    Logger\n      log() as abstract\n        -> msg as String\n        <- rtn as String?\n\n  defines class\n\n    ConsoleLogger with trait of Logger\n      override log()\n        -> msg as String\n        <- rtn as String: \"[CONSOLE] \" + msg\n\n      default operator ?\n\n    PrefixLogger with trait of Logger\n      logPrefix as String: \"[INFO]\"\n\n      PrefixLogger()\n        -> p as String\n        this.logPrefix: p\n\n      override log()\n        -> msg as String\n        <- rtn as String: `${logPrefix} ${msg}`\n\n      default operator ?\n\n  defines function\n\n    createLogger()\n      -> logType as String\n      <- rtn as Logger?\n      switch logType\n        case \"console\"\n          rtn: ConsoleLogger()\n        case \"prefix\"\n          rtn: PrefixLogger(\"[WARN]\")\n        default\n          rtn: ConsoleLogger()\n\n  defines program\n\n    FactoryPatternDemo()\n      stdout <- Stdout()\n\n      // === FACTORY FUNCTION ===\n\n      console <- createLogger(\"console\")\n      stdout.println(console.log(\"Hello from factory\"))\n\n      prefixed <- createLogger(\"prefix\")\n      stdout.println(prefixed.log(\"Warning message\"))\n\n      fallback <- createLogger(\"unknown\")\n      stdout.println(fallback.log(\"Fallback to default\"))\n\n      // === DYNAMIC CLASS AS PRODUCT ===\n\n      custom <- () with trait of Logger as class\n        override log()\n          -> msg as String\n          <- rtn as String: \"[CUSTOM] \" + msg\n\n      stdout.println(custom.log(\"Dynamic logger\"))","migrationContext":"Java: Factory Method pattern with abstract creator class, Abstract Factory for families, static factory methods. Python: factory functions, __init__ with type dispatch. Rust: associated functions (Type::new()), builder pattern. Go: constructor functions NewType(), no inheritance. Kotlin: companion object factory methods, sealed classes with when. EK9: factory functions returning trait types, switch/case for selection, dynamic classes for inline implementations.","keywords":["builder","concrete","construct","create","design","factory","idiom","implementation","pattern","select","switch","trait"],"primaryTopics":["factory pattern","factory method"],"typicalErrors":[{"error":"E05120","correct":"override log()","incorrect":"log()","explanation":"When implementing an abstract trait method in a class, the 'override' keyword is required. ConsoleLogger and PrefixLogger must use 'override log()' to implement Logger's abstract method. See ek9 -h E05120 for details."}],"companions":[]}
{"id":264,"category":"Design Patterns and Idioms","question":"How do I implement the adapter pattern in EK9?","url":"https://ek9.io/qa/QA0264.html","alternatePhrasings":["How do I adapt one interface to another in EK9?","How do I wrap an existing class with a new interface?","How do I translate between incompatible interfaces in EK9?"],"answer":"EK9 implements the adapter pattern using composition with trait implementation. The adapter class wraps the adaptee as a field and translates method calls from the new interface to the old one.\n\nADAPTER STRUCTURE\nThe pattern has three parts:\n  1. Adaptee: the existing class with its own interface\n  2. Target trait: the new interface clients expect\n  3. Adapter: wraps the adaptee and implements the target trait\n\nCOMPOSITION WRAPPING\nThe adapter holds the adaptee as a private field:\n  PrinterAdapter with trait of ModernOutput\n    printer as OldPrinter\nThe adapter translates ModernOutput.render() into OldPrinter.printText().\n\nMETHOD TRANSLATION\nThe adapter overrides trait methods and delegates to the adaptee:\n  override render()\n    -> content as String\n    <- rtn as String: printer.printText(content)\nClients call render() but the work is done by printText().\n\nTRAIT DELEGATION SHORTCUT\nWhen the adaptee already partially matches the target trait, use trait delegation with by to auto-forward compatible methods and only override the incompatible ones.\n\nWHEN TO USE ADAPTER\nUse when integrating code with different interfaces. The adapter translates without modifying either the adaptee or the client code.\n\nSee Q109 for composition patterns. See Q210 for trait delegation with by. See Q212 for composition over inheritance.","ek9Example":"defines module qa.patterns.adapter\n\n  defines class\n\n    // The existing class with its own interface\n    OldPrinter\n\n      printText()\n        -> text as String\n        <- rtn as String: \"[OLD] \" + text\n\n      default operator ?\n\n  defines trait\n\n    // The new interface clients expect\n    ModernOutput\n      render() as abstract\n        -> content as String\n        <- rtn as String?\n\n  defines class\n\n    // Adapter: wraps OldPrinter, implements ModernOutput\n    PrinterAdapter with trait of ModernOutput\n      printer as OldPrinter: OldPrinter()\n\n      PrinterAdapter()\n        -> p as OldPrinter\n        this.printer: p\n\n      override render()\n        -> content as String\n        <- rtn as String: printer.printText(content)\n\n      default operator ?\n\n  defines program\n\n    AdapterPatternDemo()\n      stdout <- Stdout()\n\n      // === OLD INTERFACE ===\n\n      oldPrinter <- OldPrinter()\n      stdout.println(oldPrinter.printText(\"Direct call\"))\n\n      // === ADAPTED INTERFACE ===\n\n      adapter <- PrinterAdapter(oldPrinter)\n      stdout.println(adapter.render(\"Through adapter\"))\n\n      // === CLIENT USES MODERN INTERFACE ===\n      // The client only knows about ModernOutput\n\n      outputs <- List() of ModernOutput\n      outputs += PrinterAdapter(OldPrinter())\n      outputs += PrinterAdapter(OldPrinter())\n\n      for output in outputs\n        stdout.println(output.render(\"Polymorphic call\"))","migrationContext":"Java: Adapter pattern with class wrapping or interface delegation, anonymous classes. Python: duck typing often avoids adapter, __getattr__ for delegation. Rust: newtype pattern (struct Wrapper(Inner)), impl Trait for Wrapper. Go: struct embedding, interface satisfaction. Kotlin: class delegation with by keyword. EK9: composition wrapping with trait implementation, trait delegation with by for partial adaptation.","keywords":["adapter","bridge","compatibility","compose","convert","delegate","design","idiom","interface","pattern","translate","wrap"],"primaryTopics":[],"typicalErrors":[{"error":"E05120","correct":"override render()","incorrect":"render()","explanation":"When implementing an abstract trait method in a class, the 'override' keyword is required. PrinterAdapter must use 'override render()' to implement ModernOutput's abstract render method. See ek9 -h E05120 for details."},{"error":"E05030","correct":"PrinterAdapter with trait of ModernOutput","incorrect":"PrinterAdapter extends OldPrinter","explanation":"OldPrinter is closed by default and cannot be extended. EK9 types are closed unless declared 'as open'. Use composition (wrapping as a field) and trait implementation instead of inheritance. See ek9 -h E05030 for details."}],"companions":[]}
{"id":265,"category":"Design Patterns and Idioms","question":"How do I implement a fluent API in EK9?","url":"https://ek9.io/qa/QA0265.html","alternatePhrasings":["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?"],"answer":"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.\n\nIMMUTABLE BUILDER PATTERN\nEach method returns a new builder instance with accumulated state:\n  QueryBuilder\n    query as String\n    select()\n      -> columns as String\n      <- rtn as QueryBuilder: QueryBuilder(\"SELECT \" + columns)\n    from()\n      -> table as String\n      <- rtn as QueryBuilder: QueryBuilder(query + \" FROM \" + table)\nThis is immutable: each call creates a new builder rather than mutating the current one.\n\nCHAINING CALLS\nStore intermediate results and chain:\n  qb <- QueryBuilder()\n  qb: qb.select(\"name, age\")\n  qb: qb.from(\"users\")\n  qb: qb.where(\"age > 18\")\n  stdout.println(qb.build())\n\nSTREAM PIPELINES AS FLUENT API\nEK9's stream pipelines are the idiomatic fluent pattern:\n  cat numbers | filter by isEven | map with doubleIt | collect as List of Integer\nThis is naturally fluent: each stage passes results to the next.\n\nWHY NOT RETURN THIS\nIn 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.\n\nWHEN TO USE EACH\nUse immutable builders for configuration and query construction. Use stream pipelines for data transformation. Use function composition (Q59) for processing chains.\n\nSee Q89 for list streams. See Q235 for stream operations reference. See Q237 for streams vs loops.","ek9Example":"defines module qa.patterns.fluent\n\n  defines function\n\n    isEven() as pure\n      -> num as Integer\n      <- rtn as Boolean: num mod 2 == 0\n\n    doubleIt() as pure\n      -> num as Integer\n      <- rtn as Integer: num * 2\n\n  defines class\n\n    QueryBuilder\n      query as String: String()\n\n      default QueryBuilder()\n\n      QueryBuilder()\n        -> q as String\n        this.query :=? String(q)\n\n      select()\n        -> columns as String\n        <- rtn as QueryBuilder: QueryBuilder(\"SELECT \" + columns)\n\n      from()\n        -> table as String\n        <- rtn as QueryBuilder: QueryBuilder(`${query} FROM ${table}`)\n\n      where()\n        -> condition as String\n        <- rtn as QueryBuilder: QueryBuilder(`${query} WHERE ${condition}`)\n\n      build() as pure\n        <- rtn as String: String(query)\n\n      default operator ?\n\n  defines program\n\n    FluentApiDemo()\n      stdout <- Stdout()\n\n      // === IMMUTABLE BUILDER ===\n\n      qb <- QueryBuilder()\n      qb: qb.select(\"name, age\")\n      qb: qb.from(\"users\")\n      qb: qb.where(\"age > 18\")\n      stdout.println(qb.build())\n\n      // === REUSABLE BASE ===\n\n      base <- QueryBuilder().select(\"id, name\")\n      fromUsers <- base.from(\"users\")\n      fromOrders <- base.from(\"orders\")\n      stdout.println(fromUsers.build())\n      stdout.println(fromOrders.build())\n\n      // === STREAM PIPELINE: NATURAL FLUENT ===\n\n      numbers <- [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]\n      evens <- cat numbers | filter by isEven | map with doubleIt | collect as List of Integer\n      stdout.println(`Doubled evens: ${evens}`)","migrationContext":"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":["api","builder","chain","compose","design","fluent","idiom","immutable","method","pattern","pipeline","stream"],"primaryTopics":[],"typicalErrors":[{"error":"E08180","correct":"query as String: String()","incorrect":"query as String","explanation":"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."},{"error":"E50001","correct":"qb <- QueryBuilder()","incorrect":"qbXYZ <- QueryBuilder()","explanation":"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."}],"companions":[]}
{"id":266,"category":"Design Patterns and Idioms","question":"How do I handle cross-cutting concerns in EK9?","url":"https://ek9.io/qa/QA0266.html","alternatePhrasings":["How do I add logging without modifying existing code?","How do I implement the decorator pattern in EK9?","How do I stack multiple behaviors in EK9?"],"answer":"EK9 handles cross-cutting concerns using the decorator pattern via trait delegation. Each decorator wraps a service, adds behavior before or after, then delegates to the wrapped service.\n\nSERVICE TRAIT\nDefine the service contract as a trait:\n  Service\n    execute() as abstract\n      -> request as String\n      <- rtn as String?\n\nCORE IMPLEMENTATION\nThe base service handles the actual work:\n  CoreService with trait of Service\n    override execute()\n      -> request as String\n      <- rtn as String: \"Processed: \" + request\n\nDECORATOR VIA DELEGATION\nUse trait delegation with by to create decorators:\n  LoggingService with trait of Service by delegate\n    delegate as Service\nOverride execute() to add logging before and after delegation.\n\nSTACKING DECORATORS\nWrap decorators around each other:\n  core <- CoreService()\n  logged <- LoggingService(core)\n  validated <- ValidatingService(logged)\nCalling validated.execute() runs: validation then logging then core.\n\nORDER MATTERS\nThe outermost decorator runs first. Stack in the order you want execution:\n  ValidatingService(LoggingService(CoreService()))\nThis validates first, then logs, then processes.\n\nVS AOP FRAMEWORKS\nNo separate AOP framework needed. Trait delegation with selective override provides the same cross-cutting capability with explicit, debuggable code.\n\nSee Q106 for trait basics. See Q115 for dynamic classes as lightweight decorators. See Q210 for trait delegation with by. See Q213 for mutex lock as a cross-cutting thread safety pattern.\n\nSee Q337 for transaction aspects. See Q334 for transaction delegation.","ek9Example":"defines module qa.patterns.crosscutting\n\n  defines trait\n\n    Service\n      execute() as abstract\n        -> request as String\n        <- rtn as String?\n\n  defines class\n\n    CoreService with trait of Service\n\n      override execute()\n        -> request as String\n        <- rtn as String: \"Processed: \" + request\n\n      default operator ?\n\n    // Decorator: adds logging around the delegated call\n    LoggingService with trait of Service by delegate\n      delegate as Service: CoreService()\n\n      LoggingService()\n        -> svc as Service\n        this.delegate: svc\n\n      override execute()\n        -> request as String\n        <- rtn as String?\n        Stdout().println(\"[LOG] Before: \" + request)\n        rtn: delegate.execute(request)\n        Stdout().println(\"[LOG] After: \" + $rtn)\n\n      default operator ?\n\n    // Decorator: adds validation before the delegated call\n    ValidatingService with trait of Service by delegate\n      delegate as Service: CoreService()\n\n      ValidatingService()\n        -> svc as Service\n        this.delegate: svc\n\n      override execute()\n        -> request as String\n        <- rtn as String: String()\n        if request?\n          rtn: delegate.execute(request)\n\n      default operator ?\n\n  defines program\n\n    CrossCuttingDemo()\n      stdout <- Stdout()\n\n      // === CORE SERVICE ALONE ===\n\n      core <- CoreService()\n      stdout.println(core.execute(\"plain\"))\n\n      // === SINGLE DECORATOR ===\n\n      logged <- LoggingService(core)\n      stdout.println(logged.execute(\"with-logging\"))\n\n      // === STACKED DECORATORS ===\n      // Validation runs first, then logging, then core\n\n      stacked <- ValidatingService(LoggingService(CoreService()))\n      stdout.println(stacked.execute(\"fully-decorated\"))\n\n      stdout.println(\"Cross-cutting via decorator stacking\")","migrationContext":"Java: AOP frameworks (Spring AOP, AspectJ), proxy-based decorators, annotation-driven aspects. Python: decorators (@decorator syntax), metaclasses, monkey patching. Rust: no built-in AOP, middleware pattern, tower crate for service layers. Go: middleware functions, handler wrapping. Kotlin: delegated properties, class delegation with by. EK9: trait delegation with by for decorator stacking, selective override for cross-cutting behavior, no AOP framework needed.","keywords":["advice","aspect","concern","cross-cutting","decorator","delegate","design","idiom","logging","middleware","pattern","stack","validation","weave","wrap"],"primaryTopics":[],"typicalErrors":[{"error":"E05120","correct":"override execute()","incorrect":"execute()","explanation":"When overriding a trait method in a delegating class, the 'override' keyword is required. LoggingService and ValidatingService must use 'override execute()' to customize the delegated Service method. See ek9 -h E05120 for details."},{"error":"E08180","correct":"delegate as Service: CoreService()","incorrect":"delegate as Service","explanation":"Class fields must be initialized inline with a default value. Declaring a field without an initializer triggers E08180. Provide a default like 'CoreService()' even if a constructor will override it. See ek9 -h E08180 for details."}],"companions":[]}
{"id":267,"category":"Design Patterns and Idioms","question":"What are common EK9 anti-patterns to avoid?","url":"https://ek9.io/qa/QA0267.html","alternatePhrasings":["What mistakes should I avoid when writing EK9 code?","What are bad practices in EK9?","What should I not do in EK9?"],"answer":"Here are common anti-patterns in EK9 with their corrections.\n\n1. OVERUSING ANY\nBad: Taking Any parameters everywhere loses type safety. The compiler cannot check operations at compile time and dispatchers must handle every type.\nGood: Use specific types or define a trait. When you need polymorphism, traits give you a controlled contract.\n\n2. DEEP INHERITANCE HIERARCHIES\nBad: Chains of A extends B extends C extends D create fragile, tightly coupled code. EK9 types are closed by default for this reason.\nGood: Use composition. Wrap the inner type as a field and expose only the methods you need. Use trait delegation to forward methods automatically.\n\n3. IGNORING GUARD EXPRESSIONS\nBad: Assuming a value is always set and using it directly. This leads to operating on unset values with undefined results.\nGood: Always use guard expressions for Optional and Result access. The guard ensures the block only executes when the value is set.\n\n4. EXCEPTIONS FOR EXPECTED FAILURES\nBad: Using try/catch for validation, parsing, or user input errors. Exceptions are for unexpected failures.\nGood: Use Result for expected failures. Result makes the caller handle both success and error paths explicitly.\n\n5. MUTABLE SHARED STATE\nBad: Multiple threads accessing the same mutable variable without synchronization.\nGood: Use MutexLock to protect shared state. The lock ensures only one thread accesses the state at a time.\n\n6. GIANT MONOLITHIC PROGRAMS\nBad: One massive program block with hundreds of lines doing everything.\nGood: Decompose into small, focused functions. Mark functions as pure where possible for testability and compiler verification.\n\nSee Q29 for guard expressions and tri-state. See Q212 for composition over inheritance. See Q213 for mutex lock thread safety. See Q254 for when Any is appropriate. See Q259 for Result vs exceptions. See Q313 for code smell detection including god classes and data clumps.","ek9Example":"defines module qa.patterns.antipatterns\n\n  defines trait\n\n    // GOOD: Specific trait instead of Any\n    Describable\n      describe() as abstract\n        <- rtn as String?\n\n  defines class\n\n    // GOOD: Implement the trait for type safety\n    Product with trait of Describable\n      productName as String?\n\n      default private Product()\n\n      Product()\n        -> pName as String\n        this.productName :=? String(pName)\n\n      override describe()\n        <- rtn as String: String(productName)\n\n      default operator ?\n\n    // GOOD: Composition over deep inheritance\n    OrderSummary\n      items as List of String: List() of String\n\n      addItem()\n        -> item as String\n        items += item\n\n      itemCount() as pure\n        <- rtn as Integer: length items\n\n      summary() as pure\n        <- rtn as String: `Order with ${length items} items`\n\n      default operator ?\n\n  defines program\n\n    AntiPatternsDemo()\n      stdout <- Stdout()\n\n      // === GOOD: Specific types via trait ===\n\n      product <- Product(\"Widget\")\n      stdout.println(product.describe())\n\n      // === GOOD: Composition wrapper ===\n\n      order <- OrderSummary()\n      order.addItem(\"Item A\")\n      order.addItem(\"Item B\")\n      order.addItem(\"Item C\")\n      stdout.println(order.summary())\n\n      // === GOOD: Guard expression ===\n\n      parsed <- Integer(\"not-a-number\")\n      if validNum <- parsed\n        stdout.println(`Valid: ${validNum}`)\n      else\n        stdout.println(\"Invalid input handled safely\")\n\n      // === GOOD: Guarded assignment for defaults ===\n\n      setting <- String()\n      setting :=? \"safe-default\"\n      stdout.println(`Setting: ${setting}`)\n\n      stdout.println(\"Anti-patterns avoided\")","migrationContext":"Java: null everywhere, deep inheritance (AbstractFactoryBeanProcessor), checked exception abuse, mutable shared state, God classes. Python: duck typing hides type errors, mutable default arguments, global state, bare except clauses. Go: error return values ignored, interface pollution, goroutine leaks. Rust: fighting the borrow checker instead of redesigning, unwrap() everywhere, unsafe blocks. EK9: anti-patterns center on overusing Any (losing type safety), ignoring guards (operating on unset values), and inheritance over composition (fragile hierarchies).","keywords":["anti-pattern","avoid","bad","common","design","error","idiom","mistake","pattern","pitfall","practice","smell","wrong"],"primaryTopics":[],"typicalErrors":[{"error":"E50001","correct":"order <- OrderSummary()","incorrect":"orderXYZ <- OrderSummary()","explanation":"Renaming the variable means later references to 'order' become unresolved, triggering E50001. Variable names must be consistent. See ek9 -h E50001 for details."},{"error":"E50001","correct":"product <- Product(\"Widget\")","incorrect":"productXYZ <- Product(\"Widget\")","explanation":"Renaming the variable means later references to 'product' become unresolved, triggering E50001. Variable names must be consistent. See ek9 -h E50001 for details."}],"companions":[]}
{"id":268,"category":"Security and Sanitization","question":"How does EK9 prevent common security vulnerabilities?","url":"https://ek9.io/qa/QA0268.html","alternatePhrasings":["How does EK9 address the OWASP top 10?","What security vulnerabilities does EK9 prevent by design?","How does EK9 language design prevent attacks?"],"answer":"EK9 prevents entire classes of vulnerabilities through language design decisions, not just libraries or best practices. Several OWASP top 10 categories are addressed at the language level.\n\nA01 BROKEN ACCESS CONTROL\nTypes are closed by default (no 'as open' means cannot extend). There is no reflection API to bypass access modifiers. Private fields and methods cannot be accessed externally.\n\nA03 INJECTION\nThe 'sanitized' keyword tracks tainted input at compile time. Seven runtime detectors covering 1,300+ patterns detect SQL injection, XSS, command injection, path traversal, LDAP injection, header injection, and expression injection. The compiler enforces the copy constructor pattern for processing sanitized data.\n\nA04 INSECURE DESIGN\nPure functions prevent uncontrolled side effects. The tri-state model (absent/unset/set) eliminates null pointer exceptions. No break, continue, or return statements prevents control flow bugs. Guard expressions enforce safe value access before use.\n\nA05 SECURITY MISCONFIGURATION\nConstrained types enforce valid configuration at construction time. Invalid values produce unset objects that guards catch, making misconfiguration impossible to silently propagate.\n\nA08 DATA INTEGRITY FAILURES\nImmutability by default protects data. Pure functions cannot use mutation operators (+=, -=, :=:, :~:, :^:). The :=: copy, :~: merge, and :^: replace operators have defined semantics preventing accidental data corruption.\n\nA10 SERVER-SIDE REQUEST FORGERY\nSanitized parameter tracking on URL and path parameters ensures that user-provided URLs cannot be used without going through the defensive copy pattern.\n\nSee Q29 for tri-state model. See Q54 for pure functions. See Q74 for guard expressions. See Q101 for closed types. See Q144 for no break/continue/return. See Q215 for sanitized parameters. See Q216 for threat detection. See Q218 for security best practices. See Q257 for constrained types. See Q270 for supply chain security. See Q272 for defense in depth.","ek9Example":"defines module qa.security.owasp\n\n  defines function\n\n    // Pure function prevents side effects (A04)\n    validateConfig() as pure\n      -> configValue as String\n      <- valid as Boolean: configValue?\n\n    // Sanitized parameter prevents injection (A03)\n    processExternalInput() as pure\n      -> input as sanitized String\n      <- result as String?\n\n      // Copy constructor — defensive copy\n      safeCopy <- String(input)\n      result: \"Processed: \" + safeCopy\n\n  defines program\n\n    OwaspPreventionDemo()\n      stdout <- Stdout()\n\n      // === CLOSED TYPES (A01) ===\n\n      stdout.println(\"A01: Types are closed by default\")\n      stdout.println(\"  No reflection API to bypass access\")\n\n      // === INJECTION PREVENTION (A03) ===\n\n      userInput <- \"external data\"\n      result <- processExternalInput(userInput)\n      if result?\n        stdout.println(\"A03: \" + result)\n\n      // === NO NULL (A04) ===\n\n      name <- String()\n      if name?\n        stdout.println(\"Has value\")\n      else\n        stdout.println(\"A04: Unset detected safely, no null pointer\")\n\n      // === GUARD EXPRESSIONS (A04) ===\n\n      if valid <- validateConfig(\"production\")\n        stdout.println(\"A04: Config validated via guard: \" + $valid)\n\n      // === IMMUTABILITY (A08) ===\n\n      stdout.println(\"A08: Pure functions prevent mutation operators\")\n      stdout.println(\"EK9 prevents OWASP categories by design\")","migrationContext":"Java: open by default, reflection bypasses access control, relies on OWASP libraries, NullPointerException everywhere. Python: no access control enforcement, no compile-time taint tracking, everything is mutable. Rust: ownership model prevents some categories, no taint tracking. Go: no generics until recently, no taint tracking, no purity. Kotlin: null safety helps but open by default, no sanitization. EK9: closed types, sanitized keyword, pure functions, tri-state model, no null, constrained types, guard expressions prevent multiple OWASP categories by design.","keywords":["attack","cve","design","language","owasp","prevention","protect","safe","secure","security","vulnerability"],"primaryTopics":[],"typicalErrors":[{"error":"E50001","correct":"result <- processExternalInput(userInput)","incorrect":"resultXYZ <- processExternalInput(userInput)","explanation":"Renaming the variable means later references to 'result' become unresolved, triggering E50001. Variable names must be consistent. See ek9 -h E50001 for details."},{"error":"E08120","correct":"result: \"Processed: \" + safeCopy","incorrect":"result += safeCopy","explanation":"In a pure function, the mutation operator += is forbidden. Use reassignment with : to create a new value instead. See ek9 -h E08120 for details."}],"companions":[]}
{"id":269,"category":"Security and Sanitization","question":"How does EK9 handle input validation?","url":"https://ek9.io/qa/QA0269.html","alternatePhrasings":["What input validation mechanisms does EK9 provide?","How do I validate user input in EK9?","How do constrained types and guards work together for validation?"],"answer":"EK9 provides four validation mechanisms that work together: constrained types for structural validation, guard expressions for conditional execution, require statements for preconditions, and sanitized parameters for taint tracking.\n\nCONSTRAINED TYPES\nConstrained types validate at construction time. A bare constructor ASSERTS the value is valid: a set value that violates the constraint panics at runtime (and a literal constant that provably violates is the compile error E08260), so use a bare constructor only for values you already trust:\n  EmailAddress as String constrain as\n    matches /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$/\n  port <- Port(8080)\nFor untrusted or boundary input use the fallible .of(value) factory instead. It takes the base-type value and returns an UNSET object if the value violates the constraint (or is itself unset), and never panics.\n\nGUARD EXPRESSIONS\nGuards combine assignment with set-checking in one line:\n  if email <- EmailAddress().of(userInput)\n    processEmail(email)\nThe block only executes if construction succeeds. This prevents processing of invalid data.\n\nREQUIRE STATEMENTS\nRequire validates preconditions at the start of a function:\n  require input?\n  require length input > 0\nA failed require throws an exception, enforcing the contract.\n\nSANITIZED PARAMETERS\nThe sanitized modifier tracks external input through the system:\n  -> userInput as sanitized String\nThe compiler enforces the copy constructor pattern for safe handling.\n\nFOUR LAYERS TOGETHER\nThe complete validation flow uses all four:\n  1. sanitized marks the input as external\n  2. Constrained type validates the format\n  3. Guard expression checks construction succeeded\n  4. require enforces additional business rules\nEach layer catches a different class of invalid input.\n\nSee Q215 for sanitized parameters. See Q257 for constrained types. See Q29 for unset variables and guards. See Q74 for guard expressions. See Q268 for OWASP vulnerability prevention. See Q272 for defense in depth.","ek9Example":"defines module qa.security.inputvalidation\n\n  defines type\n\n    // Constrained type: validates format at construction\n    EmailAddress as String constrain as\n      matches /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$/\n\n    // Constrained type: validates range\n    Port as Integer constrain\n      >= 1 and <= 65535\n\n  defines function\n\n    // All four validation layers\n    validateAndProcess() as pure\n      -> input as sanitized String\n      <- result as String: \"Invalid\"\n\n      // Layer 1: sanitized marks input as external (parameter)\n      // Layer 2: copy constructor + constrained type validates\n      safeCopy <- String(input)\n\n      // Layer 3: guard checks fallible construction succeeded\n      if email <- EmailAddress().of(safeCopy)\n\n        // Layer 4: require enforces business rules\n        require length $email > 5\n\n        result: \"Valid email: \" + $email\n\n  defines program\n\n    InputValidationDemo()\n      stdout <- Stdout()\n\n      // === CONSTRAINED TYPE VALIDATION ===\n\n      if email <- EmailAddress().of(\"user@example.com\")\n        stdout.println(\"Valid: \" + $email)\n\n      if badEmail <- EmailAddress().of(\"not-an-email\")\n        stdout.println(\"Should not reach here (email)\")\n      else\n        stdout.println(\"Invalid email caught by constraint\")\n\n      // === PORT RANGE VALIDATION ===\n\n      if port <- Port().of(8080)\n        stdout.println(\"Valid port: \" + $port)\n\n      if badPort <- Port().of(99999)\n        stdout.println(\"Should not reach here (port)\")\n      else\n        stdout.println(\"Invalid port caught by constraint\")\n\n      // === COMBINED VALIDATION ===\n\n      validResult <- validateAndProcess(\"user@example.com\")\n      if validResult?\n        stdout.println(validResult)\n\n      invalidResult <- validateAndProcess(\"bad\")\n      if invalidResult?\n        stdout.println(\"Should not reach here (combined)\")\n      else\n        stdout.println(\"Combined validation rejected invalid input\")","migrationContext":"Java: Bean Validation annotations, manual null checks, OWASP ESAPI. Python: no compile-time validation, runtime libraries. Rust: newtype pattern with manual validation. Go: manual if-err checks. Kotlin: nullable types and require() function. EK9: four integrated mechanisms (constrained types, guards, require, sanitized) that compose into a validation pipeline.","keywords":["check","constrain","form","guard","input","isset","null-safe","protect","require","safe","sanitize","security","validate","validation","verify"],"primaryTopics":[],"typicalErrors":[{"error":"E50001","correct":"validResult <- validateAndProcess(\"user@example.com\")","incorrect":"validResultXYZ <- validateAndProcess(\"user@example.com\")","explanation":"Renaming the variable means later references become unresolved, triggering E50001. Variable names must be consistent. See ek9 -h E50001 for details."},{"error":"E50010","correct":"EmailAddress as String constrain as\n      matches /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$/","incorrect":"ValidatedUser as UserRecord constrain as\n      matches /^[a-zA-Z]+$/","explanation":"Only built-in types like String, Integer, and Float can be constrained. Custom records and classes cannot be constrained because they do not have the comparison semantics needed for constraint evaluation. See ek9 -h E50010 for details."}],"companions":[]}
{"id":270,"category":"Security and Sanitization","question":"How does supply chain security work in EK9?","url":"https://ek9.io/qa/QA0270.html","alternatePhrasings":["How does EK9 handle dependency security?","How does EK9 prevent supply chain attacks?","How does EK9 manage package trust and verification?"],"answer":"EK9 addresses supply chain security through its package system, source-level compilation, and planned infrastructure for signed packages and SBOM generation.\n\nPACKAGE DECLARATION\nEK9 packages declare dependencies with explicit version constraints:\n  defines package\n    version 1.0.0-0\n    description \"My application\"\n    deps\n      com.example.utils 2.1.0 <= v < 3.0.0\nSemantic versioning is enforced. Range constraints prevent unexpected major version upgrades.\n\nSOURCE-LEVEL COMPILATION\nUnlike ecosystems where dependencies are opaque binaries (Java JARs, Python wheels, npm tarballs), EK9 compiles from source. You can inspect every line of code in your dependency tree. There are no hidden native binaries or obfuscated bytecode.\n\nVERSION PINNING\nExact version pinning prevents the npm left-pad problem (package deletion breaks builds) and typosquatting attacks (similarly-named malicious packages):\n  deps\n    org.trusted.lib 1.2.3 == v\n\nARCHITECTURAL VISION\nThe EK9 supply chain security roadmap includes:\n  SBOM generation: Software Bill of Materials listing all transitive dependencies\n  Cryptographic signing: Package authors sign releases, consumers verify signatures\n  Repository authorization: Restrict which repositories can provide packages\n  Dependency auditing: Automated scanning for known vulnerabilities\n\nCOMPARISON WITH INDUSTRY INCIDENTS\nnpm left-pad (2016): Single package deletion broke thousands of builds. EK9's version pinning and source compilation mitigate this.\nPyPI typosquatting: Malicious packages with similar names. Source-level compilation means every dependency is inspectable.\nLog4Shell (2021): Hidden JNDI lookup in binary dependency. Source compilation makes hidden functionality visible.\n\nSee Q7 for dependency management. See Q15 for semantic versioning. See Q218 for security best practices. See Q268 for OWASP vulnerability prevention.","ek9Example":"defines module qa.security.supplychain\n\n  defines package\n\n    version as Version: 1.0.0-0\n    description as String = \"Supply chain security demonstration\"\n\n  defines program\n\n    SupplyChainDemo()\n      stdout <- Stdout()\n\n      // === PACKAGE SECURITY ===\n\n      stdout.println(\"EK9 supply chain security features:\")\n      stdout.println(\"  1. Semantic versioning enforcement\")\n      stdout.println(\"  2. Source-level compilation\")\n      stdout.println(\"  3. Dependency version pinning\")\n      stdout.println(\"  4. Full dependency tree inspection\")\n\n      // === SOURCE COMPILATION ===\n\n      stdout.println(\"Source compilation advantages:\")\n      stdout.println(\"  No opaque binary dependencies\")\n      stdout.println(\"  Every line of code is inspectable\")\n      stdout.println(\"  No hidden native binaries\")\n\n      // === ROADMAP ===\n\n      stdout.println(\"Planned security infrastructure:\")\n      stdout.println(\"  SBOM generation\")\n      stdout.println(\"  Cryptographic package signing\")\n      stdout.println(\"  Repository authorization\")","migrationContext":"Java: Maven Central with binary JARs, no built-in signing verification, Log4Shell incident. Python: PyPI with binary wheels, frequent typosquatting. JavaScript: npm with tarballs, left-pad incident, frequent supply chain attacks. Rust: crates.io with source, better model but still vulnerable. Go: module proxy with checksums, good verification. EK9: source-level compilation, semantic versioning enforcement, planned SBOM and signing infrastructure.","keywords":["chain","dependency","migrate","npm","package","protect","safe","sbom","sign","supply","trust","version","vulnerability"],"primaryTopics":[],"typicalErrors":[{"error":"E50001","correct":"stdout <- Stdout()","incorrect":"stdoutXYZ <- Stdout()","explanation":"Renaming the variable means later references to 'stdout' become unresolved, triggering E50001. Variable names must be consistent. See ek9 -h E50001 for details."}],"companions":[]}
{"id":271,"category":"Security and Sanitization","question":"How do I handle secrets and environment configuration?","url":"https://ek9.io/qa/QA0271.html","alternatePhrasings":["How do I read environment variables in EK9?","How does EK9 handle API keys and credentials?","What is the best way to manage secrets in EK9?"],"answer":"EK9 handles secrets through the EnvVars type, which provides auto-sanitized access to environment variables. This aligns with the 12-factor app methodology and container practices.\n\nENVVARS API\nThe EnvVars type provides:\n  env <- EnvVars()\n  env.get(name)            Auto-sanitized: blocks SQL, XSS, command injection but allows system paths\n  env.unsanitizedGet(name) Raw value with no sanitization\n  env contains name        Check if variable exists\n  env.keys()               Iterate all variable names\n\nAUTO-SANITIZATION\nThe get() method runs InputSanitizer automatically. This blocks injection patterns in environment variable values while allowing legitimate system paths like /usr/bin. Use unsanitizedGet() only when you specifically need raw values.\n\nGUARD PATTERN\nCombine EnvVars with guard expressions for safe access:\n  env <- EnvVars()\n  if apiKey <- env.get(\"API_KEY\")\n    processWithKey(apiKey)\n  else\n    stderr.println(\"API_KEY not configured\")\nThe guard handles both missing variables and sanitization failures in one line.\n\nSANITIZED FORWARDING\nPass secrets to functions via sanitized parameters to prevent accidental logging:\n  connectToService()\n    -> apiKey as sanitized String\nThe compiler tracks the tainted status through the call chain.\n\nWHAT NOT TO DO\nNever embed secrets in source code. Never log secret values (sanitized tracking helps prevent this). Never store secrets in the .ek9 build directory. Never use unsanitizedGet without a specific reason.\n\nINDUSTRY ALIGNMENT\nEnvironment variables are the industry standard for secrets. Docker and Kubernetes inject secrets as env vars. Vault and AWS Secrets Manager ultimately deliver values through env vars. The 12-factor app methodology specifies env vars for configuration. EK9's EnvVars type with auto-sanitization goes beyond what most languages provide.\n\nSee Q215 for sanitized parameters. See Q29 for unset variables and guard patterns. See Q74 for guard expressions. See Q268 for OWASP vulnerability prevention. See Q272 for defense in depth.","ek9Example":"defines module qa.security.secrets\n\n  defines function\n\n    // Accept sanitized secret for processing\n    connectToService() as pure\n      -> apiKey as sanitized String\n      <- result as String: \"No key provided\"\n\n      // Copy constructor for defensive copy\n      safeKey <- String(apiKey)\n      if safeKey?\n        result: \"Connected with key length: \" + $length safeKey\n\n  defines program\n\n    SecretsAndEnvDemo()\n      stdout <- Stdout()\n      stderr <- Stderr()\n\n      // === READ ENVIRONMENT VARIABLES ===\n\n      env <- EnvVars()\n\n      // Auto-sanitized access with guard\n      if path <- env.get(\"PATH\")\n        stdout.println(\"PATH available, length: \" + $length path)\n      else\n        stdout.println(\"PATH not available\")\n\n      // === CHECK EXISTENCE ===\n\n      if env contains \"HOME\"\n        stdout.println(\"HOME is configured\")\n\n      // === GUARD PATTERN FOR SECRETS ===\n\n      if apiKey <- env.get(\"API_KEY\")\n        // Pass as sanitized to maintain tracking\n        result <- connectToService(apiKey)\n        if result?\n          stdout.println(result)\n      else\n        stderr.println(\"API_KEY not configured\")\n\n      // === ITERATE AVAILABLE KEYS ===\n\n      stdout.println(\"Environment variable access patterns:\")\n      stdout.println(\"  get() — auto-sanitized\")\n      stdout.println(\"  unsanitizedGet() — raw value\")\n      stdout.println(\"  contains — check existence\")\n      stdout.println(\"  keys() — iterate names\")","migrationContext":"Java: System.getenv() returns raw String, no sanitization. Python: os.environ with no safety. Go: os.Getenv returns raw string. Rust: std::env::var returns raw string. Kotlin: System.getenv() with no sanitization. EK9: EnvVars.get() auto-sanitizes blocking injection patterns, unsanitizedGet() for raw access, guard pattern for missing variables.","keywords":["api","config","credential","env","environment","key","password","protect","safe","secret","security","sensitive","token","variable"],"primaryTopics":[],"typicalErrors":[{"error":"E50001","correct":"env <- EnvVars()","incorrect":"envXYZ <- EnvVars()","explanation":"Renaming the variable means later references to 'env' become unresolved, triggering E50001. Variable names must be consistent. See ek9 -h E50001 for details."},{"error":"E50001","correct":"result <- connectToService(apiKey)","incorrect":"resultXYZ <- connectToService(apiKey)","explanation":"Renaming the variable means later references to 'result' become unresolved, triggering E50001. Variable names must be consistent. See ek9 -h E50001 for details."}],"companions":[]}
{"id":272,"category":"Security and Sanitization","question":"How do I build defense in depth in EK9?","url":"https://ek9.io/qa/QA0272.html","alternatePhrasings":["How do EK9 security features work together?","How do I create a layered security pipeline in EK9?","How do I combine sanitized, constrained, require, and pure in EK9?"],"answer":"Defense in depth means applying multiple security layers so that if one layer fails, the next catches the problem. EK9's security features compose naturally into a layered pipeline.\n\nLAYER 1: SANITIZED ENTRY POINT\nMark untrusted input at the system boundary:\n  processRequest()\n    -> input as sanitized String\nThe compiler tracks this input through all subsequent operations.\n\nLAYER 2: STRUCTURAL VALIDATION\nUse a constrained type to validate the format:\n  ValidEmail as String constrain as\n    matches /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$/\nValidate untrusted input with the fallible factory ValidEmail().of(value): it produces an unset object on a constraint failure (a bare ValidEmail(value) instead ASSERTS validity and Panics on a set invalid value).\n\nLAYER 3: GUARD CHECK\nUse a guard expression to verify validation succeeded:\n  safeCopy <- String(input)\n  if email <- ValidEmail().of(safeCopy)\n    // Only proceeds with structurally valid data\n\nLAYER 4: BUSINESS VALIDATION\nUse require for domain-specific rules:\n  require length $email > 5\n  require $email contains \"@company.com\"\nFailed require throws, preventing invalid business data.\n\nLAYER 5: PURE PROCESSING\nProcess validated data in a pure function:\n  pureTransform() as pure\n    -> data as String\n    <- result as String?\nPurity prevents side effects and mutation during processing.\n\nLAYER 6: OUTPUT GUARD\nGuard the output before use:\n  if result <- pureTransform(cleanData)\n    stdout.println(result)\nEnsures processing produced a valid result.\n\nEach layer catches a different failure mode: Layer 1 catches missing sanitization at compile time. Layer 2 catches malformed input. Layer 3 prevents processing unset values. Layer 4 enforces business rules. Layer 5 prevents side effects. Layer 6 catches processing failures.\n\nSee Q54 for pure functions. See Q74 for guard expressions. See Q215 for sanitized parameters. See Q218 for security best practices. See Q257 for constrained types. See Q269 for input validation. See Q273 for purity as security boundary.","ek9Example":"defines module qa.security.defenseindepth\n\n  defines type\n\n    // Layer 2: Structural validation\n    ValidEmail as String constrain as\n      matches /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$/\n\n  defines function\n\n    // Layer 5: Pure processing\n    pureTransform() as pure\n      -> content as String\n      <- result as String: \"Empty\"\n\n      if content?\n        result: \"Processed: \" + content\n\n    // Complete defense-in-depth pipeline\n    processRequest()\n      // Layer 1: sanitized entry point\n      -> input as sanitized String\n      <- result as String: \"Rejected\"\n\n      // Layer 2+3: Copy, validate via the fallible factory, and guard\n      safeCopy <- String(input)\n      if email <- ValidEmail().of(safeCopy)\n\n        // Layer 4: Business validation\n        require length $email > 5\n\n        // Layer 5: Pure processing\n        if processed <- pureTransform($email)\n\n          // Layer 6: Output guard\n          result: processed\n\n  defines program\n\n    DefenseInDepthDemo()\n      stdout <- Stdout()\n\n      // === FULL PIPELINE ===\n\n      validResult <- processRequest(\"user@example.com\")\n      if validResult?\n        stdout.println(validResult)\n\n      // === LAYER 2 CATCHES BAD FORMAT ===\n\n      invalidResult <- processRequest(\"not-email\")\n      if invalidResult?\n        stdout.println(\"Should not reach here\")\n      else\n        stdout.println(\"Bad format caught by constrained type\")\n\n      // === LAYERS SUMMARY ===\n\n      stdout.println(\"Defense in depth layers:\")\n      stdout.println(\"  1. sanitized — marks untrusted input\")\n      stdout.println(\"  2. Constrained type — validates format\")\n      stdout.println(\"  3. Guard — checks construction\")\n      stdout.println(\"  4. require — business rules\")\n      stdout.println(\"  5. Pure — no side effects\")\n      stdout.println(\"  6. Output guard — validates result\")","migrationContext":"Java: defense in depth requires manual layering of OWASP ESAPI, Bean Validation, Spring Security. Python: no compile-time enforcement, runtime-only layers. Rust: ownership model provides some layers but no taint tracking. Go: manual error checking at each layer. Kotlin: null safety as one layer, but no sanitization or purity. EK9: sanitized, constrained types, guards, require, and pure compose into a natural defense-in-depth pipeline with compile-time enforcement.","keywords":["chain","combine","compile","defense","depth","layer","migrate","multi","pipeline","protect","safe","security","stack"],"primaryTopics":[],"typicalErrors":[{"error":"E50001","correct":"validResult <- processRequest(\"user@example.com\")","incorrect":"validResultXYZ <- processRequest(\"user@example.com\")","explanation":"Renaming the variable means later references to 'validResult' become unresolved, triggering E50001. Variable names must be consistent. See ek9 -h E50001 for details."},{"error":"E50001","correct":"invalidResult <- processRequest(\"not-email\")","incorrect":"invalidResultXYZ <- processRequest(\"not-email\")","explanation":"Renaming the variable means later references to 'invalidResult' become unresolved, triggering E50001. Variable names must be consistent. See ek9 -h E50001 for details."}],"companions":[]}
{"id":273,"category":"Security and Sanitization","question":"How does purity create security boundaries?","url":"https://ek9.io/qa/QA0273.html","alternatePhrasings":["Why does purity matter for security in EK9?","How does EK9 pragmatic purity prevent security bugs?","How do pure functions protect against data corruption?"],"answer":"EK9 pragmatic purity is focused on preventing mutation-based attacks and data corruption. It is not Haskell-style 'no side effects' but a practical security boundary.\n\nWHAT PURE PREVENTS\nMutation operators are forbidden in pure functions:\n  += -= *= /= :=: :~: :^:\nThese modify the left-hand side in place and could affect the right-hand side as a side effect. Calling user-defined impure functions is also blocked.\n\nWHAT PURE ALLOWS\nReassignment with : (creates new value):\n  result: result * i\nLoop variables naturally change:\n  for i in 1 ... n\nReturn variables are reassigned, not mutated. ALL I/O is allowed because I/O types carry the IO marker trait. Stdout, Stderr, Stdin, EnvVars, TextFile, TCP, and UDP are all usable in pure functions.\n\nDATA INTEGRITY\nThe key insight is that + creates a new value while += mutates in place. In pure functions, only + is allowed. This means parameters passed to a pure function cannot be modified as a side effect:\n  pureProcess() as pure\n    -> data as String\n    result <- \"Prefix: \" + data\n    // 'data' is guaranteed untouched\n\nDEFENSIVE COPY ENFORCEMENT\nWith sanitized parameters in pure context, ONLY the copy constructor works:\n  secureProcess() as pure\n    -> input as sanitized String\n    localCopy <- String(input)\n    // Must use localCopy, cannot mutate input\nPurity and sanitization together force the safest possible handling.\n\nCONTROLLED CALL CHAINS\nPure functions can only call other pure functions. This creates a trust chain: if the entry point is pure, every function in the chain is pure. No impure operation can hide in the middle.\n\nTOCTOU PREVENTION\nTime-of-check-time-of-use attacks require mutating shared state between the check and the use. Since mutation operators are blocked in pure functions, the checked value cannot change before it is used.\n\nCONSUMER vs ACCEPTOR\nConsumer of T is pure (read-only access). Acceptor of T is impure (can mutate). This distinction lets APIs express whether a callback can modify data:\n  process()\n    -> handler as Consumer of String\n    // handler CANNOT modify anything\n\nSee Q54 for pure functions and Consumer/Acceptor. See Q217 for sanitized parameter mechanics in pure context. See Q218 for security best practices. See Q241 for mutation operators. See Q272 for defense in depth. See Q560 for purity contracts and enforcement rules. See Q635 for forbidden mutation operators in pure methods.","ek9Example":"defines module qa.security.puritysecurity\n\n  defines function\n\n    // Pure function with I/O — allowed\n    pureWithIo() as pure\n      -> message as String\n      stdout <- Stdout()\n      stdout.println(\"Pure I/O: \" + message)\n\n    // Pure function — reassignment, not mutation\n    pureCalculate() as pure\n      -> n as Integer\n      <- result as Integer: 1\n\n      for i in 1 ... n\n        // result: result * i — reassignment with new value\n        // result *= i — would be BLOCKED (mutation operator)\n        result: result * i\n\n    // Pure + sanitized = forced copy pattern\n    pureSecureProcess() as pure\n      -> input as sanitized String\n      <- result as String?\n\n      // Only copy constructor works here\n      localCopy <- String(input)\n      result: \"Secure: \" + localCopy\n\n    // Consumer is pure — read only\n    pureConsumerExample() as pure\n      -> handler as Consumer of String\n      handler(\"data\")\n\n  defines program\n\n    PuritySecurityDemo()\n      stdout <- Stdout()\n\n      // === PURE WITH I/O ===\n\n      pureWithIo(\"I/O is allowed in pure functions\")\n\n      // === PURE CALCULATION ===\n\n      factorial <- pureCalculate(5)\n      stdout.println(\"5! = \" + $factorial)\n\n      // === PURE + SANITIZED ===\n\n      secureResult <- pureSecureProcess(\"external input\")\n      if secureResult?\n        stdout.println(secureResult)\n\n      // === CONSUMER vs ACCEPTOR ===\n\n      stdout.println(\"Consumer of T: pure, read-only\")\n      stdout.println(\"Acceptor of T: impure, can mutate\")\n\n      // === SECURITY BENEFITS ===\n\n      stdout.println(\"Purity security benefits:\")\n      stdout.println(\"  No mutation operators in pure\")\n      stdout.println(\"  Parameters cannot be modified as side effect\")\n      stdout.println(\"  Forced defensive copies with sanitized\")\n      stdout.println(\"  Pure call chains create trust boundaries\")\n      stdout.println(\"  TOCTOU prevented by no shared mutation\")","migrationContext":"Java: no purity concept, any method can mutate any reachable object. Python: no purity enforcement, convention only. Haskell: strict purity but no I/O in pure (uses IO monad). Rust: immutable borrows prevent mutation but no purity concept. Go: no purity. Kotlin: no purity enforcement. EK9: pragmatic purity blocks mutation operators while allowing I/O, creating security boundaries that prevent data corruption and TOCTOU attacks.","keywords":["audit","data","define","immutable","integrity","migrate","mutation","operator","pragmatic","protect","pure","purity","safe","security","side-effect","toctou"],"primaryTopics":[],"typicalErrors":[{"error":"E08120","correct":"result: result * i","incorrect":"result *= i","explanation":"In a pure function, the mutation operator *= is forbidden. Use reassignment with : to create a new value (result: result * i). See ek9 -h E08120 for details."},{"error":"E50001","correct":"factorial <- pureCalculate(5)","incorrect":"factorialXYZ <- pureCalculate(5)","explanation":"Renaming the variable means later references to 'factorial' become unresolved, triggering E50001. Variable names must be consistent. See ek9 -h E50001 for details."},{"error":"E50001","correct":"secureResult <- pureSecureProcess(\"external input\")","incorrect":"secureResultXYZ <- pureSecureProcess(\"external input\")","explanation":"Renaming the variable means later references to 'secureResult' become unresolved, triggering E50001. Variable names must be consistent. See ek9 -h E50001 for details."}],"companions":[]}
{"id":274,"category":"What AI Gets Wrong About EK9","question":"Why does AI generate return statements in EK9?","url":"https://ek9.io/qa/QA0274.html","alternatePhrasings":["Why does my AI assistant use return in EK9 code?","How do I fix AI-generated return statements in EK9?","What replaces the return keyword in EK9?"],"answer":"AI language models trained on Java, Python, and Rust confidently generate return statements in EK9. This is one of the most common AI hallucinations because every mainstream language uses return. EK9 has NO return keyword. It does not exist in the grammar.\n\nTHE AI MISTAKE\nAI generates code like 'return result', 'return \"hello\"', 'return;', or early return patterns like 'if invalid then return'. None of these compile in EK9. The keyword does not exist.\n\nTHE EK9 WAY\nDeclare a named return variable with '<-' and the compiler ensures all code paths initialise it:\n  greet() as pure\n    -> name as String\n    <- message as String: \"Hello, \" + name\nThe return variable 'message' is declared, initialised, and automatically returned. No return statement needed.\n\nCONDITIONAL RETURNS\nWhen you need different values on different paths, declare a default then modify:\n  classify() as pure\n    -> score as Integer\n    <- label as String: \"average\"\n    if score >= 90\n      label: \"excellent\"\n    else if score < 40\n      label: \"poor\"\nThe compiler verifies every path initialises 'label'. No early return needed.\n\nREPLACING EARLY RETURN\nAI often generates early return for precondition checks. In EK9, use structured control flow:\n  process() as pure\n    -> items as List of String\n    <- count as Integer: 0\n    if ~items?\n      count: -1\n    else\n      count: length items\nAll paths explicitly set the return variable. The compiler enforces completeness.\n\nGUARD EXPRESSIONS\nFor conditional processing where you only proceed if a value is set:\n  lookup()\n    -> key as String\n    <- found as String: String()\n    if record <- findRecord(key)\n      found: $record\nThe guard 'if record <- findRecord(key)' checks isSet and assigns in one step. If unset, the block is skipped.\n\nWHY REMOVED\nReturn statements create hidden exit points. Functions with multiple returns are harder to reason about, harder to debug, and produce unreachable code. Named return variables make every path explicit and verifiable.\n\nSee Q50 for declared return variables. See Q144 for why return was removed. See Q281 for verifying AI code. See Q289 for before and after migration patterns.","ek9Example":"defines module qa.ai.mistakes.returnstatement\n\n  defines function\n\n    greet() as pure\n      -> name as String\n      <- message as String: \"Hello, \" + name\n\n    classify() as pure\n      -> score as Integer\n      <- label as String: \"average\"\n      excellentThreshold <- 90\n      poorThreshold <- 40\n      if score >= excellentThreshold\n        label: \"excellent\"\n      else if score < poorThreshold\n        label: \"poor\"\n\n    countItems() as pure\n      -> items as List of String\n      <- count as Integer: 0\n      if ~items?\n        count: -1\n      else\n        count: length items\n\n  defines program\n\n    ReturnMistakeDemo()\n      stdout <- Stdout()\n\n      // === SIMPLE RETURN ===\n\n      stdout.println(greet(\"Steve\"))\n\n      // === CONDITIONAL RETURN ===\n\n      stdout.println(`Score 95: ${classify(95)}`)\n      stdout.println(`Score 30: ${classify(30)}`)\n      stdout.println(`Score 60: ${classify(60)}`)\n\n      // === REPLACING EARLY RETURN ===\n\n      names <- [\"Alice\", \"Bob\"]\n      stdout.println(`Count: ${countItems(names)}`)\n\n      emptyList <- List() of String\n      stdout.println(`Empty count: ${countItems(emptyList)}`)","migrationContext":"Java: return statement required, early return common for preconditions. Python: return statement, implicit None if omitted. Rust: implicit last expression or explicit return. Go: named returns exist but return still required. JavaScript: return statement, undefined if omitted. EK9: NO return statement, named return variable with '<-', compiler verifies all paths, guard expressions replace early return.","keywords":["ai","common-error","declare","early","exit","hallucination","migrate","mistake","pitfall","precondition","return","statement","variable","wrong"],"primaryTopics":[],"typicalErrors":[{"error":"E01072","correct":"<- message as String: \"Hello, \" + name","incorrect":"return \"Hello, \" + name","explanation":"EK9 has no return keyword. Declare a named return variable with '<-' instead. The compiler enforces all code paths initialise it. See ek9 -h E01072 for details."},{"error":"E01072","correct":"<- label as String: \"average\"","incorrect":"return \"average\"","explanation":"EK9 uses declared return variables, not return statements. Assign different values on different code paths and the compiler verifies completeness. See ek9 -h E01072 for details."},{"error":"E01072","correct":"label: \"excellent\"","incorrect":"return \"excellent\"","explanation":"AI generates early return for conditional paths. In EK9, assign to the declared return variable instead. The compiler verifies all paths initialise it. No early exit needed. See ek9 -h E01072 for details."}],"companions":[]}
{"id":275,"category":"What AI Gets Wrong About EK9","question":"Why does AI use break and continue in EK9?","url":"https://ek9.io/qa/QA0275.html","alternatePhrasings":["Why does my AI assistant generate break in EK9 loops?","How do I replace AI-generated break and continue in EK9?","What does EK9 use instead of break and continue?"],"answer":"AI models trained on C, Java, and Python generate break and continue in EK9 loops. These keywords do not exist in EK9. They are not in the grammar. The compiler cannot parse them.\n\nTHE AI MISTAKE\nAI generates 'break' to exit a loop early and 'continue' to skip items. Both are invalid EK9.\n\nTHE EK9 PHILOSOPHY\nFor loops in EK9 are designed as complete-iteration constructs. They run from start to finish. If you need selective or partial processing, use stream pipelines instead.\n\nFILTER REPLACES CONTINUE\nWhere AI writes a loop with continue to skip items, use filter:\n  cat items | filter by isValid | collect as List of String\nFilter selects only matching elements. Non-matching elements are skipped. This is declarative: you say WHAT you want, not HOW to skip.\n\nHEAD REPLACES BREAK\nWhere AI writes a loop with break to take the first N items, use head:\n  cat items | head 5 | collect as List of String\nHead takes the first N elements and stops. No loop exit needed.\n\nTAIL AND SKIP\nFor other slicing patterns:\n  cat items | tail 3 | collect as List of String\n  cat items | skip 2 | collect as List of String\nTail takes the last N. Skip discards the first N.\n\nFOR-IN RUNS FULLY\nThe for-in loop processes every element:\n  for item in items\n    stdout.println(item)\nThis is the intended pattern. Every item is processed. If you need to process only some items, filter first, then iterate.\n\nEVIDENCE\nMicrosoft (2011): 15% of production bugs in C# involved break and continue errors. Apple SSL bug (2014): duplicated goto bypassed SSL validation. Linux Kernel: 200+ CVE fixes from break-in-wrong-loop bugs.\n\nSee Q89 for stream pipelines. See Q125 for head, tail, and skip. See Q144 for the design decision. See Q145 for replacing break and continue. See Q281 for verifying AI code. See Q288 for before and after migration patterns.","ek9Example":"defines module qa.ai.mistakes.breakcontinue\n\n  defines function\n\n    isLong() as pure\n      -> item as String\n      <- longer <- Boolean()\n      minLength <- 3\n      longer: length item > minLength\n\n  defines program\n\n    BreakContinueDemo()\n      stdout <- Stdout()\n\n      items <- [\"ax\", \"hello\", \"by\", \"world\", \"no\", \"stream\"]\n\n      // === FILTER REPLACES CONTINUE ===\n      // AI would write: for item in items { if length(item) <= 3 then continue; process(item) }\n      // EK9 way: filter to only long items\n\n      longItems <- cat items | filter by isLong | collect as List of String\n      for item in longItems\n        stdout.println(`Long item: ${item}`)\n\n      // === HEAD REPLACES BREAK ===\n      // AI would write: for item in items { count++; if count >= 2 then break }\n      // EK9 way: take first 2\n\n      firstTwo <- cat items | head 2 | collect as List of String\n      for item in firstTwo\n        stdout.println(`First two: ${item}`)\n\n      // === COMBINED: FILTER THEN HEAD ===\n\n      firstLong <- cat items | filter by isLong | head 1 | collect as List of String\n      for item in firstLong\n        stdout.println(`First long: ${item}`)\n\n      // === FOR-IN RUNS FULLY (the intended pattern) ===\n\n      for item in items\n        stdout.println(`All: ${item}`)","migrationContext":"Java: break and continue in loops, labelled break for nested loops. Python: break and continue in loops. Rust: break and continue with loop labels. Go: break and continue. C/C++: break and continue, major bug source. EK9: NO break or continue, use stream pipelines with filter (skip items), head (take first N), tail (take last N), skip (discard first N). For loops run fully by design.","keywords":["ai","break","common-error","continue","exit","filter","hallucination","head","loop","migrate","mistake","pitfall","skip","stream","tail","wrong"],"primaryTopics":[],"typicalErrors":[{"error":"E01071","correct":"stdout.println(`Long item: ${item}`)","incorrect":"continue","explanation":"EK9 has no continue keyword. It does not exist in the grammar. Use stream pipelines with filter to skip items instead of loops with continue. See ek9 -h E01071 for details."},{"error":"E01070","correct":"stdout.println(`First two: ${item}`)","incorrect":"break","explanation":"EK9 has no break keyword. It does not exist in the grammar. Use head N in a stream pipeline to take the first N elements instead of loops with break. See ek9 -h E01070 for details."}],"companions":[]}
{"id":276,"category":"What AI Gets Wrong About EK9","question":"Why does AI confuse declarations and assignments in EK9?","url":"https://ek9.io/qa/QA0276.html","alternatePhrasings":["Why does AI generate 'let x = 5' or 'var x = 5' in EK9?","How do declarations and assignments differ in EK9?","What are the EK9 assignment operators?"],"answer":"AI models generate 'x = 5', 'let x = 5', 'var x = 5', or 'val x = 5' in EK9. None of these are valid EK9 declarations. AI conflates two distinct concepts: creating a new variable (declaration) and changing an existing variable (assignment).\n\nTHE AI MISTAKE\nAI does not understand EK9's distinction between declaration and assignment. It imports syntax from JavaScript (let/const), Kotlin (val/var), Rust (let), or Java (type name = value).\n\nEK9 DECLARATIONS\nCreate a new variable with '<-' (type inferred) or explicit type with ':' initialiser:\n  name <- \"hello\"\n  age <- 42\n  score as Float: 9.5\n  greeting as String: \"hi\"\nThe '<-' operator creates AND initialises. The 'as Type:' form declares with explicit type.\n\nEK9 ASSIGNMENTS\nAssign to an EXISTING variable with ':', ':=', or '=':\n  name: \"world\"\n  name := \"world\"\n  name = \"world\"\nAll three have IDENTICAL semantics. The choice is stylistic:\n  ':' is concise, natural in loop bodies, switch branches, if/else blocks\n  ':=' is stronger, highlights significant or deliberate assignments\n  '=' is also valid, same semantics as the other two\n\nGUARDED ASSIGNMENT\nThe ':=?' operator has DIFFERENT semantics from the other three. It only assigns if the variable is currently UNSET:\n  config <- String()\n  config :=? \"default\"\n  config :=? \"other\"\nAfter this, config is \"default\". The second ':=?' does nothing because config is already set.\n\nTHE COMPILER ENFORCES THE DISTINCTION\nUsing '<-' on an existing variable is an error. Using ':' or ':=' on a non-existent variable is an error. The compiler knows whether you are declaring or assigning.\n\nSee Q24 for variable declaration. See Q29 for guarded assignment. See Q168 for fallback values. See Q243 for coalescing operators. See Q281 for verifying AI code.","ek9Example":"defines module qa.ai.mistakes.assignment\n\n  defines program\n\n    AssignmentDemo()\n      stdout <- Stdout()\n\n      // === DECLARATIONS WITH <- ===\n\n      name <- \"Alice\"\n      age <- 30\n      active <- true\n\n      stdout.println(`Declared: ${name}, ${age}, ${active}`)\n\n      // === EXPLICIT TYPE DECLARATIONS ===\n\n      greeting as String: \"Hello\"\n      limit as Integer: 100\n\n      stdout.println(`Typed: ${greeting}, ${limit}`)\n\n      // === ASSIGNMENTS (all three identical) ===\n\n      name: \"Bob\"\n      stdout.println(`After ':' assignment: ${name}`)\n\n      name := \"Charlie\"\n      stdout.println(`After ':=' assignment: ${name}`)\n\n      name = \"Diana\"\n      stdout.println(`After '=' assignment: ${name}`)\n\n      // === GUARDED ASSIGNMENT :=? ===\n\n      config <- String()\n      stdout.println(`Before guard: config is set? ${config?}`)\n\n      config :=? \"default\"\n      stdout.println(`After first guard: ${config}`)\n\n      config :=? \"other\"\n      stdout.println(`After second guard: ${config}`)\n\n      // === STYLISTIC CHOICE IN CONTEXT ===\n\n      total <- 0\n      for num in [1, 2, 3, 4, 5]\n        total: total + num\n      stdout.println(`Loop total with ':': ${total}`)\n\n      strategy <- \"none\"\n      strategy := \"aggressive\"\n      stdout.println(`Strategy with ':=': ${strategy}`)","migrationContext":"Java: 'Type name = value' for declaration, 'name = value' for assignment, same '=' symbol for both. JavaScript: 'let x = 5', 'const x = 5', 'x = 5'. Kotlin: 'val x = 5' (immutable), 'var x = 5' (mutable). Rust: 'let x = 5', 'let mut x = 5'. Python: 'x = 5' for both declaration and assignment. Go: ':=' for short declaration, '=' for assignment. EK9: '<-' for declaration (type inferred), 'as Type:' for explicit type, ':', ':=', '=' for assignment (all identical), ':=?' for guarded assignment (different semantics).","keywords":["ai","assignment","common-error","declaration","declare","equals","hallucination","infer","let","migrate","mistake","operator","pitfall","syntax","var","variable","wrong"],"primaryTopics":[],"typicalErrors":[{"error":"E50001","correct":"name <- \"Alice\"","incorrect":"name = \"Alice\"","explanation":"Using '=' attempts to assign to an existing variable, but 'name' was never declared. In EK9, '<-' creates a new variable (declaration), while '=', ':=', and ':' assign to an EXISTING variable. AI trained on Python uses '=' for both, but EK9 enforces the distinction. See ek9 -h E50001 for details."}],"companions":[]}
{"id":277,"category":"What AI Gets Wrong About EK9","question":"Why does AI generate null checks instead of guards in EK9?","url":"https://ek9.io/qa/QA0277.html","alternatePhrasings":["Why does AI use 'if x != null' in EK9?","How do I replace AI-generated null checks in EK9?","What does EK9 use instead of null?"],"answer":"AI models generate 'if result != null', 'if result is not None', or 'if result == null return' in EK9. There is no null in EK9. The keyword does not exist. EK9 uses a tri-state model (absent, unset, set) with guard expressions.\n\nTHE AI MISTAKE\nAI imports null-checking patterns from Java (null), Python (None), JavaScript (null/undefined), or Go (nil). None of these concepts exist in EK9.\n\nTHE EK9 TRI-STATE MODEL\nEvery EK9 object exists in one of three states:\n  Absent: does not exist (missing Dict key, empty Optional)\n  Present but unset: exists but has no meaningful value (String() creates unset String)\n  Present and set: exists with a valid value (String(\"hello\") is set)\n\nTHE ? OPERATOR\nCheck whether an object is set with the ? operator:\n  if name?\n    stdout.println(name)\nThis replaces null checks. The ? operator returns Boolean.\n\nGUARD EXPRESSIONS\nGuards combine assignment with the ? check in a single step:\n  if record <- findRecord(key)\n    stdout.println(`Found: ${record}`)\nThe guard 'if record <- findRecord(key)' declares 'record', assigns the result, and checks if it is set. If unset, the block is skipped entirely. No null check needed.\n\nGUARDS WORK EVERYWHERE\nThe same guard syntax works identically in all control flow:\n  while connection <- getActive()\n    transferData(connection)\n  for entry <- nextEntry()\n    process(entry)\n  switch config <- loadConfig()\n    case .port > 8000\n      useHighPort(config)\n\nGUARDED ASSIGNMENT FOR DEFAULTS\nThe ':=?' operator assigns only if the target is unset:\n  host <- String()\n  host :=? \"localhost\"\nThis replaces the null coalescing pattern (Java's 'x != null ? x : default').\n\nSee Q29 for tri-state semantics. See Q74 for guard expressions. See Q85 for guards in all control flow. See Q243 for coalescing operators. See Q281 for verifying AI code.","ek9Example":"defines module qa.ai.mistakes.nullchecks\n\n  defines function\n\n    findByName() as pure\n      -> searchName as String\n      <- found as String: String()\n      if searchName == \"Alice\"\n        found: \"Alice: Engineer\"\n\n    getPort() as pure\n      <- port as Integer: 8080\n\n  defines program\n\n    NullCheckDemo()\n      stdout <- Stdout()\n\n      // === GUARD EXPRESSION REPLACES NULL CHECK ===\n      // AI would write: result = findByName(\"Alice\"); if (result != null) print(result)\n      // EK9 way: guard expression\n\n      if record <- findByName(\"Alice\")\n        stdout.println(`Found: ${record}`)\n\n      if record <- findByName(\"Unknown\")\n        stdout.println(\"Found someone\")\n      else\n        stdout.println(\"Not found, guard skipped the block\")\n\n      // === ? OPERATOR REPLACES NULL CHECK ===\n      // AI would write: if (name != null) print(name)\n      // EK9 way: ? operator\n\n      name <- String()\n      if name?\n        stdout.println(\"Has a name\")\n      else\n        stdout.println(\"Name is unset (not null, just unset)\")\n\n      name: findByName(\"Steve\")\n      if name?\n        stdout.println(`Name is set: ${name}`)\n\n      // === GUARDED ASSIGNMENT REPLACES NULL COALESCING ===\n      // AI would write: host = host != null ? host : \"localhost\"\n      // EK9 way: :=? guarded assignment\n\n      host <- String()\n      host :=? \"localhost\"\n      stdout.println(`Host: ${host}`)\n\n      host :=? \"other\"\n      stdout.println(`Still: ${host}`)\n\n      // === GUARD WITH FUNCTION RETURN ===\n\n      if port <- getPort()\n        stdout.println(`Port: ${port}`)","migrationContext":"Java: null checks everywhere, NullPointerException is most common exception, Optional since Java 8. Python: 'if x is not None', AttributeError from None. JavaScript: null AND undefined, loose equality traps. Kotlin: nullable types String?, safe call ?.  operator, Elvis ?:. Rust: Option<T> with match/if-let. Go: nil checks. EK9: NO null, tri-state model (absent/unset/set), ? operator checks isSet, guard expressions combine check and assignment, :=? guarded assignment for defaults.","keywords":["absent","ai","check","common-error","guard","hallucination","isset","migrate","mistake","nil","none","null","null-safe","pitfall","safe","set","tristate","unset","wrong"],"primaryTopics":[],"typicalErrors":[{"error":"E01073","correct":"if record <- findByName(\"Alice\")","incorrect":"if findByName(\"Alice\") != null","explanation":"EK9 has no null keyword. Use guard expressions with '<-' to check if a value is set and assign it in one step. See ek9 -h E01073 for details."},{"error":"E01073","correct":"if name?","incorrect":"if name != null","explanation":"EK9 uses the ? operator to check if an object is set, not null comparisons. The ? operator returns Boolean. See ek9 -h E01073 for details."},{"error":"E01073","correct":"host :=? \"localhost\"","incorrect":"host = host != null <- host : \"localhost\"","explanation":"EK9 uses the guarded assignment operator :=? which only assigns if the variable is currently unset. This replaces null coalescing patterns. See ek9 -h E01073 for details."}],"companions":[]}
{"id":278,"category":"What AI Gets Wrong About EK9","question":"Why does AI try to extend List, Dict, or closed types in EK9?","url":"https://ek9.io/qa/QA0278.html","alternatePhrasings":["Why does AI generate 'MyList extends List of String' in EK9?","How do I fix AI-generated class extension of closed types?","Why are EK9 types closed by default?"],"answer":"AI models trained on Java generate 'class MyList extends ArrayList' or 'class TypedMap extends HashMap' patterns. In EK9, types are CLOSED by default. Built-in collection types (List, Dict, Optional, Result, PriorityQueue) cannot be extended. Attempting to extend them produces error E05030.\n\nTHE AI MISTAKE\nAI generates 'MyList extends List of String' or 'SafeDict extends Dict of (String, Integer)'. This fails because List and Dict are closed types. The AI is importing the Java pattern where ArrayList and HashMap are open.\n\nTHE EK9 WAY: COMPOSITION\nWrap the collection as a private field and expose only the operations you need:\n  TaskQueue\n    items as List of String\n    addTask()\n      -> taskName as String\n      items += taskName\n    pending() as pure\n      <- count as Integer: length items\nThe List is hidden. Clients interact with TaskQueue, not List.\n\nWHY CLOSED BY DEFAULT\nClosed types prevent mixing collection behavior with application logic. If you extend List, callers get both your custom API and every List method. This creates confusing, hard-to-maintain interfaces. Composition gives you complete control over the public API.\n\nADDING CUSTOM VALIDATION\nComposition lets you add business rules that inheritance cannot:\n  ValidatedList\n    items as List of String\n    add()\n      -> entry as String\n      if entry?\n        items += entry\nOnly set (valid) strings are added. With inheritance, callers could bypass your add method and use the parent List methods directly.\n\nEXPOSING ITERATION\nDelegate to the wrapped collection when needed:\n  allTasks() as pure\n    <- tasks as List of String: List(items)\nReturn a copy to prevent external modification of internal state.\n\nMODERN PRECEDENT\nSwift structs are closed. Rust has no inheritance. Kotlin classes are final by default. EK9 follows this modern trend. Types require explicit 'as open' to allow extension.\n\nSee Q96 for class definitions. See Q212 for composition over inheritance. See Q210 for trait delegation. See Q281 for verifying AI code.","ek9Example":"defines module qa.ai.mistakes.closedtypes\n\n  defines class\n\n    // === COMPOSITION WRAPPING A LIST ===\n\n    TaskQueue\n      items as List of String: List() of String\n\n      default TaskQueue()\n\n      addTask()\n        -> taskName as String\n        if taskName?\n          items += taskName\n\n      pending() as pure\n        <- count as Integer: length items\n\n      allTasks() as pure\n        <- tasks as List of String: items + List() of String\n\n      override operator ? as pure\n        <- isSet as Boolean: items?\n\n      operator $ as pure\n        <- asString as String: `TaskQueue(${pending()} tasks)`\n\n    // === COMPOSITION WRAPPING A DICT ===\n\n    Settings\n      entries as Dict of (String, String): Dict() of (String, String)\n\n      default Settings()\n\n      put()\n        ->\n          key as String\n          setting as String\n        if key? and setting?\n          entries += DictEntry(key, setting)\n\n      get() as pure\n        -> key as String\n        <- setting as String: entries.getOrDefault(key, \"\")\n\n      has() as pure\n        -> key as String\n        <- exists as Boolean: entries.contains(key)\n\n      override operator ? as pure\n        <- isSet as Boolean: entries?\n\n  defines program\n\n    ClosedTypeDemo()\n      stdout <- Stdout()\n\n      // === USE TASKQUEUE (COMPOSITION OVER INHERITANCE) ===\n\n      queue <- TaskQueue()\n      queue.addTask(\"Deploy\")\n      queue.addTask(\"Test\")\n      queue.addTask(\"Review\")\n\n      stdout.println($queue)\n      stdout.println(`Pending: ${queue.pending()}`)\n\n      tasks <- queue.allTasks()\n      for taskItem in tasks\n        stdout.println(`  Task: ${taskItem}`)\n\n      // === USE SETTINGS (COMPOSITION OVER INHERITANCE) ===\n\n      prefs <- Settings()\n      prefs.put(\"theme\", \"dark\")\n      prefs.put(\"lang\", \"en\")\n\n      stdout.println(`Theme: ${prefs.get(\"theme\")}`)\n      stdout.println(`Has lang: ${prefs.has(\"lang\")}`)\n      stdout.println(`Missing: ${prefs.get(\"missing\")}`)","migrationContext":"Java: ArrayList and HashMap are open, commonly extended (considered bad practice). Python: list is open, commonly subclassed. C++: virtual inheritance, open by default. Rust: no inheritance, composition is the pattern. Go: embedding for delegation, no inheritance. Kotlin: classes final by default like EK9. Swift: structs are closed. EK9: ALL types closed by default, 'as open' required for extension, built-in collections cannot be extended, composition is the natural pattern.","keywords":["ai","closed","common-error","composition","delegate","dict","exhaustive","extend","hallucination","inherit","list","migrate","mistake","open","pitfall","sealed","wrong"],"primaryTopics":[],"typicalErrors":[{"error":"E05030","correct":"TaskQueue\n      items as List of String","incorrect":"TaskQueue extends List of String\n      items as List of String","explanation":"EK9 types are closed by default. List, Dict, Optional, Result, and PriorityQueue cannot be extended. Use composition instead: wrap the collection as a private field. See ek9 -h E05030 for details."},{"error":"E05030","correct":"Settings\n      entries as Dict of (String, String)","incorrect":"Settings extends Dict of (String, String)\n      entries as Dict of (String, String)","explanation":"Dict is a closed type and cannot be subclassed. Wrap it as a field and expose domain-specific methods instead. See ek9 -h E05030 for details."}],"companions":[]}
{"id":279,"category":"What AI Gets Wrong About EK9","question":"Why does AI confuse EK9 operators with other languages?","url":"https://ek9.io/qa/QA0279.html","alternatePhrasings":["Why does AI generate toString() instead of the $ operator?","What are the correct EK9 operator equivalents?","How do I fix AI-generated method calls that should be operators?"],"answer":"AI models map Java and Python method names to EK9 when operators should be used. EK9 has a fixed set of operators with specific symbols. AI frequently generates method calls that do not exist.\n\nCOMMON AI MISTAKES AND CORRECT EK9\n  .toString() does not exist. Use $ (the string operator). Always returns String.\n  .toJson() does not exist. Use $$ (the JSON operator). Always returns JSON.\n  .hashCode() does not exist. Use #? (the hashcode operator). Returns Integer.\n  .equals(other) does not exist. Use == (equality operator). Returns Boolean.\n  .clone() does not exist. Use :=: (copy operator) or copy constructor MyType(original).\n  .isPresent() does not exist. Use ? (isSet operator). Returns Boolean.\n  !x for logical NOT is wrong for Boolean. Use ~x (tilde, only for Boolean and Bits).\n  x instanceof T does not exist. Use traits and dispatching instead.\n\nOPERATOR DEFINITIONS IN A CLASS\nWhen defining operators on your own types, use the 'operator' keyword:\n  operator $ as pure\n    <- rtn as String: ...\n  operator #? as pure\n    <- rtn as Integer: ...\n  operator == as pure\n    -> other as MyType\n    <- rtn as Boolean: ...\n  operator <=> as pure\n    -> other as MyType\n    <- rtn as Integer: ...\n\nNOT-EQUAL\nBoth != and <> are valid for not-equal in EK9. AI often uses only != which works, but <> is also correct and preferred in some contexts.\n\nCOPY PATTERNS\nTwo ways to copy: the :=: operator mutates the target, the copy constructor creates a new instance:\n  target :=: source\n  copied <- MyType(original)\n\nSee Q238 for the complete operator set. See Q239 for comparison operators. See Q240 for arithmetic operators. See Q241 for mutation operators. See Q281 for verifying AI code.","ek9Example":"defines module qa.ai.mistakes.operators\n\n  defines class\n\n    // === Class to test $ operator confusion ===\n\n    Labelled\n      label as String: String()\n\n      Labelled()\n        -> initialLabel as String\n        label: initialLabel\n\n      operator $ as pure\n        <- rtn as String: String(label)\n\n      override operator ? as pure\n        <- rtn as Boolean: label?\n\n    // === Class to test #? operator confusion ===\n\n    Coded\n      code as Integer: Integer()\n\n      Coded()\n        -> initialCode as Integer\n        code: initialCode\n\n      operator #? as pure\n        <- rtn as Integer: Integer(code)\n\n      override operator ? as pure\n        <- rtn as Boolean: code?\n\n    // === Class to test == operator confusion ===\n\n    Ranked\n      rank as Integer: Integer()\n\n      Ranked()\n        -> initialRank as Integer\n        rank: initialRank\n\n      operator == as pure\n        -> other as Ranked\n        <- rtn as Boolean: rank == other.rank\n\n      override operator ? as pure\n        <- rtn as Boolean: rank?\n\n  defines program\n\n    OperatorConfusionDemo()\n      stdout <- Stdout()\n\n      // === $ REPLACES toString() ===\n\n      item <- Labelled(\"hello\")\n      stdout.println(`Item: ${item}`)\n\n      // === #? REPLACES hashCode() ===\n\n      coded <- Coded(42)\n      hash <- #?coded\n      stdout.println(`Hash: ${hash}`)\n\n      // === == REPLACES equals() ===\n\n      rankA <- Ranked(1)\n      rankB <- Ranked(1)\n      stdout.println(`Equal: ${rankA == rankB}`)\n\n      // === ~ FOR BOOLEAN NOT ===\n\n      active <- true\n      inactive <- ~active\n      stdout.println(`Active: ${active}, Inactive: ${inactive}`)","migrationContext":"Java: toString(), hashCode(), equals(), clone(), instanceof, !boolean. Python: str(), hash(), __eq__, copy.copy(), isinstance(), not. Rust: Display trait, Hash trait, PartialEq, Clone, matches!, !bool. Go: Stringer interface, no operator overloading. Kotlin: toString(), hashCode(), equals(), !boolean. EK9: $ for string, $$ for JSON, #? for hashcode, == for equality, :=: for copy, ? for isSet, ~ for NOT (Boolean/Bits only), traits and dispatching replace instanceof.","keywords":["ai","clone","common-error","confuse","equals","hallucination","hashcode","instanceof","isset","migrate","mistake","operator","pitfall","tostring","wrong"],"primaryTopics":[],"typicalErrors":[{"error":"E04020","correct":"operator $ as pure","incorrect":"toString() as pure","explanation":"EK9 has no toString() method. Use the $ operator for string conversion. Without it, the type cannot be used in string interpolation. See ek9 -h E04020 for details."},{"error":"E07620","correct":"operator #? as pure","incorrect":"hashCode() as pure","explanation":"EK9 has no hashCode() method. Use the #? operator for hash code computation. Without it, the #? prefix operator is not available on the type. See ek9 -h E07620 for details."},{"error":"E07620","correct":"operator == as pure","incorrect":"equals() as pure","explanation":"EK9 has no equals() method. Use the == operator for equality comparison. Without it, the == infix operator is not available on the type. See ek9 -h E07620 for details."},{"error":"E50001","correct":"item <- Labelled(\"hello\")","incorrect":"itemXYZ <- Labelled(\"hello\")","explanation":"Renaming the variable means later references to 'item' become unresolved, triggering E50001. Variable names must be consistent. See ek9 -h E50001 for details."},{"error":"E50001","correct":"coded <- Coded(42)","incorrect":"codedXYZ <- Coded(42)","explanation":"Renaming the variable means later references to 'coded' become unresolved, triggering E50001. Variable names must be consistent. See ek9 -h E50001 for details."}],"companions":[]}
{"id":280,"category":"What AI Gets Wrong About EK9","question":"Why does AI generate getters and setters in EK9?","url":"https://ek9.io/qa/QA0280.html","alternatePhrasings":["Why does AI use getName() and setName() in EK9?","What is the EK9 convention for accessors?","How does EK9 handle information hiding without getters?"],"answer":"AI models trained on Java generate getName(), setName(), getValue(), isActive() patterns. Nothing is syntactically wrong with these names in EK9, but they violate EK9 conventions and miss the deeper design philosophy.\n\nTHE AI MISTAKE\nAI generates Java bean methods: getName(), setName(value), getContent(), isActive(). EK9 does not follow this naming convention.\n\nEK9 ACCESSOR CONVENTION\nUse bare noun or verb names:\n  content() not getContent()\n  keys() not getKeys()\n  hue() not getHue()\n  trim() not getTrim()\n\nEK9 SETTER ALTERNATIVE\nUse bare noun with parameter or 'with' prefix:\n  content(newContent) not setContent(newContent)\n  withContent(newContent) for returning a modified copy\n\nTHE DEEPER ISSUE\nGet and set patterns encourage breaking information hiding. You should be interested in a class's BEHAVIOR, not its internal state. Classes and components should keep internal state private and expose operations that make sense in the domain.\n\nEXTERNAL STATE REPRESENTATION\nIf external state is needed, return a RECORD with copies of an external representation, not copies of internal state variables. The record is a deliberate public-facing view, decoupled from internals. If the internal structure changes, the record stays stable.\n\nOPERATORS NOT GETTERS\nFor standard conversions, use operators:\n  $ for string representation (replaces toString/getName patterns)\n  $$ for JSON representation\n  ? for state checking (replaces isActive/isValid patterns)\n\nONLY ENUMERATIONS HAVE AUTOMATIC OPERATORS\nOnly enumerations have full automatic operator generation. Records and classes can use 'default operator' to opt in, but this is explicit, not automatic.\n\nSee Q96 for classes. See Q98 for records. See Q238 for operator overview. See Q281 for verifying AI code.","ek9Example":"defines module qa.ai.mistakes.getterssetters\n\n  defines record\n\n    // Record as external state representation\n\n    AccountSummary\n      holder as String: String()\n      balance as Float: Float()\n\n      AccountSummary() as pure\n        ->\n          theHolder as String\n          theBalance as Float\n        holder :=? theHolder\n        balance :=? theBalance\n\n      default operator\n\n  defines class\n\n    // Class exposes BEHAVIOR, not internal state\n\n    BankAccount\n      ownerName as String: String()\n      currentBalance as Float: Float()\n      isOpen as Boolean: false\n\n      BankAccount()\n        ->\n          initialOwner as String\n          initialBalance as Float\n        ownerName: initialOwner\n        currentBalance: initialBalance\n        isOpen: true\n\n      // Bare noun accessor — NOT getOwnerName()\n      owner() as pure\n        <- rtn as String: String(ownerName)\n\n      // Behavior method — NOT setBalance()\n      deposit()\n        -> amount as Float\n        if amount > 0.0 and isOpen\n          currentBalance: currentBalance + amount\n\n      withdraw()\n        -> amount as Float\n        <- success as Boolean: false\n        if amount > 0.0 and amount <= currentBalance and isOpen\n          currentBalance: currentBalance - amount\n          success: true\n\n      closeAccount()\n        isOpen: false\n\n      // Return a RECORD as external state view\n      summary() as pure\n        <- rtn as AccountSummary: AccountSummary(ownerName, currentBalance)\n\n      // $ operator replaces toString()\n      operator $ as pure\n        <- rtn as String: `${ownerName}: ${currentBalance}`\n\n      // ? operator replaces isActive()\n      override operator ? as pure\n        <- rtn as Boolean: ownerName? and isOpen\n\n  defines program\n\n    GetterSetterDemo()\n      stdout <- Stdout()\n\n      account <- BankAccount(\"Alice\", 1000.0)\n\n      // === BARE NOUN ACCESSOR — NOT getName() ===\n\n      stdout.println(`Owner: ${account.owner()}`)\n\n      // === BEHAVIOR METHODS — NOT setBalance() ===\n\n      account.deposit(500.0)\n      stdout.println(`After deposit: ${account}`)\n\n      if ok <- account.withdraw(200.0)\n        stdout.println(`Withdrew 200: ${account}`)\n\n      // === RECORD AS EXTERNAL STATE VIEW ===\n\n      view <- account.summary()\n      stdout.println(`Summary: ${view}`)\n\n      // === ? OPERATOR REPLACES isActive() ===\n\n      stdout.println(`Account active: ${account?}`)\n      account.closeAccount()\n      stdout.println(`After close, active: ${account?}`)","migrationContext":"Java: JavaBean convention (getName/setName), IDE generates getters/setters, Lombok @Data generates them. Python: @property decorator, direct attribute access common. Rust: no getter convention, pub fields or methods. Go: exported fields (uppercase) or accessor methods. Kotlin: properties with get/set, data classes auto-generate. C#: properties with get/set accessors. EK9: bare noun accessors (content() not getContent()), operators for standard conversions ($ for string, ? for state), expose behavior not state, records for external state representation.","keywords":["accessor","ai","bean","behavior","common-error","encapsulation","field","getter","hallucination","hiding","information","migrate","mistake","pitfall","property","setter","wrong"],"primaryTopics":[],"typicalErrors":[{"error":"E50001","correct":"account <- BankAccount(\"Alice\", 1000.0)","incorrect":"accountXYZ <- BankAccount(\"Alice\", 1000.0)","explanation":"Renaming the variable means later references to 'account' become unresolved, triggering E50001. Variable names must be consistent. See ek9 -h E50001 for details."},{"error":"E50001","correct":"view <- account.summary()","incorrect":"viewXYZ <- account.summary()","explanation":"Renaming the variable means later references to 'view' become unresolved, triggering E50001. Variable names must be consistent. See ek9 -h E50001 for details."}],"companions":[]}
{"id":281,"category":"What AI Gets Wrong About EK9","question":"How do I verify AI-generated EK9 code?","url":"https://ek9.io/qa/QA0281.html","alternatePhrasings":["How do I check if AI-generated EK9 code is correct?","What is the workflow for validating AI EK9 output?","How do I use the compiler to review AI code?"],"answer":"The EK9 compiler catches ALL common AI mistakes. Three-step verification:\n\nSTEP 1: COMPILE — Run 'ek9 -c file.ek9'. Catches syntax, types, operators, code flow.\n\nSTEP 2: FIX ERRORS — Use 'ek9 -h String', 'ek9 -h List' for correct APIs. Use 'ek9 -q \"topic\"' for patterns.\n\nSTEP 3: TEST — Use @Test and assert, run 'ek9 -t file.ek9' for logic errors.\n\nCOMMON AI FIXES\n  'return x' -> '<- rtn as Type: x'\n  'break' -> 'head N' in stream\n  'if x != null' -> 'if x <- expr()' (guard)\n  'extends List' -> delegation\n  '.toString()' -> '$' operator\n  lambda -> dynamic function\n\nSee Q274-Q280 for specific AI mistake categories. See Q52 for dynamic functions. See Q115 for dynamic classes. See Q156 for assert.","ek9Example":"defines module qa.ai.mistakes.verify\n\n  defines function\n\n    // Declared return — AI would write 'return'\n    classify() as pure\n      -> score as Integer\n      <- rating as String: \"average\"\n      excellentThreshold <- 90\n      poorThreshold <- 40\n      if score >= excellentThreshold\n        rating: \"excellent\"\n      else if score < poorThreshold\n        rating: \"poor\"\n\n    // Pure predicate for stream filter — replaces 'continue'\n    isPositive() as pure\n      -> number as Integer\n      <- positive as Boolean: number > 0\n\n    // Abstract function type — dynamic functions implement these\n    transformer() as pure abstract\n      -> input as Integer\n      <- output as Integer?\n\n  defines trait\n\n    Formatter\n      format() as abstract\n        -> input as String\n        <- rtn as String?\n\n  defines class\n\n    // Composition — AI would write 'extends List of String'\n    NameList\n      items as List of String: List() of String\n\n      default NameList()\n\n      add()\n        -> entry as String\n        if entry?\n          items += entry\n\n      count() as pure\n        <- total as Integer: length items\n\n      allNames() as pure\n        <- names as List of String: items + List() of String\n\n      // $ operator — AI would write toString()\n      operator $ as pure\n        <- rtn as String: `NameList(${count()} names)`\n\n      // ? operator — AI would write isPresent() or isActive()\n      override operator ? as pure\n        <- rtn as Boolean: items?\n\n  defines program\n\n    VerifyAiCodeDemo()\n      stdout <- Stdout()\n\n      // === DECLARED RETURN (not 'return') ===\n\n      stdout.println(`Score 95: ${classify(95)}`)\n      stdout.println(`Score 30: ${classify(30)}`)\n\n      // === STREAM PIPELINE (not break/continue) ===\n\n      numbers <- [-2, 5, -1, 8, 0, 3]\n      positives <- cat numbers | filter by isPositive | head 2 | collect as List of Integer\n      for num in positives\n        stdout.println(`Positive: ${num}`)\n\n      // === GUARD EXPRESSION (not null check) ===\n\n      if rating <- classify(75)\n        stdout.println(`Rating: ${rating}`)\n\n      // === COMPOSITION (not extends List) ===\n\n      names <- NameList()\n      names.add(\"Alice\")\n      names.add(\"Bob\")\n      stdout.println($names)\n      stdout.println(`Count: ${names.count()}`)\n\n      // === GUARDED ASSIGNMENT (not null coalescing) ===\n\n      config <- String()\n      config :=? \"production\"\n      stdout.println(`Config: ${config}`)\n\n      // === ? OPERATOR (not isPresent/isActive) ===\n\n      stdout.println(`Names set: ${names?}`)\n\n      // === DYNAMIC FUNCTION (not lambda '(x) -> x + x') ===\n\n      doubler <- () is transformer as pure function\n        output:=? input + input\n\n      testInput <- 21\n      stdout.println(`Doubled: ${doubler(testInput)}`)\n\n      // === UNNAMED DYNAMIC CLASS (not 'new Interface() { }') ===\n\n      tag <- \">>>\"\n      fmtHandler <- (tag) with trait of Formatter as class\n        override format()\n          -> input as String\n          <- rtn as String: `${tag} ${input}`\n        default operator ?\n\n      stdout.println(fmtHandler.format(\"hello\"))\n\n      // === NAMED DYNAMIC CLASS (not inline 'class Pair { }') ===\n\n      userName <- \"Carol\"\n      userAge <- 30\n      person <- PersonInfo(label: userName, personAge: userAge) as class\n        describe() as pure\n          <- rtn as String: `${label} age ${personAge}`\n        operator $ as pure\n          <- rtn as String: describe()\n        default operator ?\n\n      stdout.println($person)\n\n      // === BARE NOUN ACCESSOR (not getCount/getNames) ===\n\n      allItems <- names.allNames()\n      for item in allItems\n        stdout.println(`  Name: ${item}`)\n\n    // === STEP 6: WRITE TESTS TO VERIFY AI LOGIC ===\n\n    @Test\n    VerifyClassify()\n      assert classify(95) == \"excellent\"\n      assert classify(30) == \"poor\"\n      assert classify(60) == \"average\"\n\n    @Test\n    VerifyNameList()\n      names <- NameList()\n      names.add(\"Alice\")\n      names.add(\"Bob\")\n      expectedCount <- 2\n      assert names.count() == expectedCount\n      assert names?\n\n    @Test\n    VerifyDynamicFunction()\n      doubler <- () is transformer as pure function\n        output:=? input + input\n      testInput <- 21\n      expectedResult <- 42\n      assert doubler(testInput) == expectedResult\n\n    @Test\n    VerifyDynamicClass()\n      tag <- \">>>\"\n      fmtHandler <- (tag) with trait of Formatter as class\n        override format()\n          -> input as String\n          <- rtn as String: `${tag} ${input}`\n        default operator ?\n      expectedOutput <- \">>> test\"\n      assert fmtHandler.format(\"test\") == expectedOutput","migrationContext":"Java/Python/JS: no equivalent compile-time AI code verification. EK9: compiler catches ALL common AI mistakes with specific error codes, built-in help and Q&A system for correct patterns.","keywords":["ai","anonymous","assistant","capture","check","class","code","common-error","compile","correct","dynamic","error","function","hallucination","lambda","mistake","pitfall","review","tool","trait","validate","verify"],"primaryTopics":[],"typicalErrors":[{"error":"E50001","correct":"names <- NameList()","incorrect":"namesXYZ <- NameList()","explanation":"Renaming the variable means later references to 'names' become unresolved, triggering E50001. Variable names must be consistent. See ek9 -h E50001 for details."},{"error":"E50001","correct":"config <- String()","incorrect":"configXYZ <- String()","explanation":"Renaming the variable means later references to 'config' become unresolved, triggering E50001. Variable names must be consistent. See ek9 -h E50001 for details."},{"error":"E50001","correct":"tag <- \">>>\"","incorrect":"tagXYZ <- \">>>\"","explanation":"Renaming the variable means later references to 'tag' become unresolved, triggering E50001. Variable names must be consistent. See ek9 -h E50001 for details."},{"error":"E05120","correct":"override format()\n          -> input as String\n          <- rtn as String: `${tag} ${input}`","incorrect":"format()\n          -> input as String\n          <- rtn as String: `${tag} ${input}`","explanation":"Methods implementing trait abstracts in a dynamic class must use 'override' keyword. Without it the method shadows the parent rather than implementing it. See ek9 -h E05120 for details."}],"companions":[]}
{"id":282,"category":"Control Flow Without break/continue/return","question":"How do I exit a nested loop without break or labels?","url":"https://ek9.io/qa/QA0282.html","alternatePhrasings":["How do I stop both inner and outer loops without break in EK9?","What replaces labelled break for nested loops in EK9?","How do I search a grid or matrix without break in EK9?"],"answer":"EK9 has no break, no labels, and no goto. When you need to exit nested loops, the solution is function decomposition: extract the inner loop into its own function.\n\nTHE PROBLEM\nIn Java you write: outer: for (row : grid) { for (item : row) { if (found(item)) break outer; } }. In Python you use flag variables or exceptions. These patterns are error-prone and obscure intent.\n\nPATTERN 1: DECOMPOSE INTO FUNCTIONS\nExtract the inner loop into a separate function that returns unset if nothing found:\n  searchRow()\n    -> row as List of String, target as String\n    <- found as String: String()\n    for item in row\n      if item == target\n        found: item\nThen guard on the result in the outer loop:\n  for row in grid\n    if match <- searchRow(row, target)\n      result: match\nThe outer loop naturally stops assigning once result is set, and the inner function handles one level of nesting.\n\nPATTERN 2: FLATTEN AND STREAM\nFor simple search across nested data, flatten with concatenation and use filter + head:\n  allItems <- cat row1 + row2 + row3 | filter by isTarget | head 1 | collect as List of String\nThis avoids nested loops entirely by working with a flat pipeline.\n\nPATTERN 3: WHILE WITH GUARD\nUse a flag to control the outer loop:\n  located <- Boolean()\n  rowIndex <- 0\n  while rowIndex < length grid and ~located?\n    row <- grid.getOrDefault(rowIndex, List() of String)\n    for item in row\n      if item == target\n        located: true\n    rowIndex: rowIndex + 1\nThe while loop exits when located becomes set.\n\nKEY PRINCIPLE\nIf you need nested break, your function is doing too much. Decompose it. Each function handles one level of iteration.\n\nSee Q144 for why break was removed. See Q145 for basic break replacement. See Q146 for function decomposition.","ek9Example":"defines module qa.without.nestedloop\n\n  defines function\n\n    searchRow()\n      ->\n        row as List of String\n        target as String\n      <- found as String: String()\n      for item in row\n        if item == target\n          found: item\n\n    isMatch() as pure\n      -> item as String\n      <- matched as Boolean: item == \"cherry\"\n\n  defines program\n\n    NestedLoopDemo()\n      stdout <- Stdout()\n\n      // === PATTERN 1: DECOMPOSE INTO FUNCTIONS ===\n\n      row1 <- [\"apple\", \"banana\", \"cherry\"]\n      row2 <- [\"date\", \"elderberry\", \"fig\"]\n      row3 <- [\"grape\", \"honeydew\", \"kiwi\"]\n      grid <- [row1, row2, row3]\n\n      target <- \"cherry\"\n      result <- String()\n      for row in grid\n        if match <- searchRow(row, target)\n          result :=? match\n      if result?\n        stdout.println(`Found by decomposition: ${result}`)\n      else\n        stdout.println(\"Not found by decomposition\")\n\n      // === PATTERN 2: FLATTEN AND STREAM ===\n\n      allItems <- row1 + row2 + row3\n      streamResult <- cat allItems | filter by isMatch | head 1 | collect as List of String\n      stdout.println(`Found by stream: ${streamResult}`)\n\n      // === PATTERN 3: WHILE WITH GUARD ===\n\n      located <- Boolean()\n      rowIndex <- 0\n      while rowIndex < length grid and ~located?\n        row <- grid.getOrDefault(rowIndex, List() of String)\n        for item in row\n          if item == target\n            located: true\n        rowIndex: rowIndex + 1\n      stdout.println(`Located by while: ${located?}`)\n\n      // === SEARCH FOR MISSING ITEM ===\n\n      missing <- String()\n      for row in grid\n        if match <- searchRow(row, \"mango\")\n          missing :=? match\n      if ~missing?\n        stdout.println(\"Mango not found as expected\")","migrationContext":"Java: break with label (break outer;) for nested loops. Python: flag variables or raise/except hack. Rust: break with loop labels ('outer: loop { break 'outer; }). Go: break with label, or goto. C/C++: goto or flag variables. Kotlin: break with label (@outer). JavaScript: break with label. EK9: decompose inner loop into a function, guard on result, or flatten and stream.","keywords":["alternative","break","decompose","exit","flatten","function","grid","inner","label","loop","matrix","migrate","nested","no-break","no-return","outer","search"],"primaryTopics":["exit nested loop","nested loop","break alternative"],"typicalErrors":[{"error":"E01070","correct":"result :=? match","incorrect":"break","explanation":"EK9 has no break statement. To exit a nested loop, decompose into functions and use guard assignment (:=?) so the first successful result wins. See ek9 -h E01070 for details."}],"companions":[]}
{"id":283,"category":"Control Flow Without break/continue/return","question":"How do I implement retry logic without break?","url":"https://ek9.io/qa/QA0283.html","alternatePhrasings":["How do I retry an operation up to N times without break in EK9?","What replaces while(true) with break for retry loops in EK9?","How do I write a bounded retry loop in EK9?"],"answer":"Every language uses while(true) { try... break } for retries. EK9 has no break AND no while(true). Use a counter-bounded for-range loop with a guard on success.\n\nTHE PROBLEM\nIn Java you write: while (true) { result = tryOp(); if (result != null) break; retries++; if (retries >= max) break; }. This has two break conditions, an infinite loop, and requires careful counting.\n\nEK9 PATTERN: FOR-RANGE WITH GUARD\nUse a for-range loop bounded by max attempts and guard on the result:\n  retryOperation()\n    -> maxRetries as Integer\n    <- result as String: String()\n    for attempt in 1 ... maxRetries\n      if ~result?\n        result :=? tryOnce(attempt)\nThe for-range is bounded by design (no infinite loop). The guard on ~result? skips further attempts once a result is set. The :=? ensures only the first successful result is kept.\n\nWHY BETTER THAN BREAK\nThe loop is bounded by construction. There is no infinite loop risk. The intent is clear: try up to N times, stop on first success. The ~result? check at the top of each iteration replaces the break-on-success pattern.\n\nSIMPLIFIED PATTERN\nFor simpler cases where the operation returns a set or unset value:\n  for attempt in 1 ... maxRetries\n    if ~result?\n      outcome <- tryOnce(attempt)\n      result :=? outcome\nThe :=? assigns only if result is still unset, so the first success wins.\n\nSee Q134 for try-catch patterns. See Q144 for why break was removed. See Q64 for for-range loops.","ek9Example":"defines module qa.without.retry\n\n  defines function\n\n    tryOnce() as pure\n      -> attempt as Integer\n      <- outcome as String: String()\n      successAttempt <- 3\n      if attempt == successAttempt\n        outcome: \"success on attempt 3\"\n\n    tryAlwaysFails() as pure\n      -> attempt as Integer\n      <- outcome as String: String()\n      require attempt?\n\n    retryOperation()\n      -> maxRetries as Integer\n      <- result as String: String()\n      for attempt in 1 ... maxRetries\n        if ~result?\n          outcome <- tryOnce(attempt)\n          result :=? outcome\n\n    retryExhausted()\n      -> maxRetries as Integer\n      <- result as String: String()\n      for attempt in 1 ... maxRetries\n        if ~result?\n          outcome <- tryAlwaysFails(attempt)\n          result :=? outcome\n\n  defines program\n\n    RetryDemo()\n      stdout <- Stdout()\n\n      // === RETRY WITH SUCCESS ON 3RD ATTEMPT ===\n\n      successResult <- retryOperation(5)\n      if successResult?\n        stdout.println(`Retry succeeded: ${successResult}`)\n      else\n        stdout.println(\"Retry failed\")\n\n      // === RETRY WITH ALL ATTEMPTS FAILING ===\n\n      failResult <- retryExhausted(5)\n      if failResult?\n        stdout.println(`Unexpected success: ${failResult}`)\n      else\n        stdout.println(\"All 5 attempts failed as expected\")\n\n      // === INLINE RETRY PATTERN ===\n\n      inlineResult <- String()\n      for attempt in 1 ... 4\n        if ~inlineResult?\n          minSuccessAttempt <- 2\n          if attempt >= minSuccessAttempt\n            inlineResult :=? \"inline success\"\n      stdout.println(`Inline retry: ${inlineResult}`)","migrationContext":"Java: while(true) { try { result = op(); break; } catch { retries++; if (retries >= max) break; } }. Python: while True: try: result = op(); break; except: retries += 1. Rust: loop { match try_op() { Ok(r) => break r, Err(_) => retries += 1 } }. Go: for retries := 0; retries < max; retries++ { result, err := op(); if err == nil { break } }. EK9: for-range with guard on result, :=? for first-success-wins, no infinite loops possible.","keywords":["alternative","attempt","bounded","fail","guard","isset","loop","maximum","migrate","no-break","no-return","null-safe","repeat","resilient","retry","safe","success","tries"],"primaryTopics":[],"typicalErrors":[{"error":"E01070","correct":"result :=? outcome","incorrect":"break","explanation":"EK9 has no break statement. For retry logic, use a for-range bounded loop with guard assignment (:=?) so the first success wins without needing break. See ek9 -h E01070 for details."}],"companions":[]}
{"id":284,"category":"Control Flow Without break/continue/return","question":"How do I find the first match and stop searching without break?","url":"https://ek9.io/qa/QA0284.html","alternatePhrasings":["How do I do early termination search without break in EK9?","How do I return the first matching item from a list in EK9?","How do I search with transformation without break in EK9?"],"answer":"For simple first-match, use filter + head 1 in a stream pipeline. For complex search with transformation or context, use a function with unset return and a for loop.\n\nSIMPLE FIRST MATCH: FILTER + HEAD\nThe basic pattern for finding the first match:\n  first <- cat items | filter by predicate | head 1 | collect as List of String\nThis stops processing after the first match. No break needed.\n\nPATTERN 1: SEARCH WITH TRANSFORMATION\nFind the first match and return a TRANSFORMED result, not the match itself:\n  findAndTransform()\n    -> items as List of String\n    <- result as String: String()\n    for item in items\n      if length item > 5 and ~result?\n        result :=? item + \" (long)\"\nThe :=? ensures only the first long item gets transformed and assigned.\n\nPATTERN 2: SEARCH WITH INDEX\nFind an item AND its position:\n  findWithIndex()\n    -> items as List of String, target as String\n    <- position as Integer: Integer()\n    idx <- 0\n    for item in items\n      if item == target and ~position?\n        position :=? idx\n      idx: idx + 1\nThe caller gets the index of the first occurrence, or an unset Integer if not found.\n\nPATTERN 3: MULTI-FIELD SEARCH\nSearch objects by multiple criteria using a helper predicate:\n  isMatch() as pure\n    -> item as String\n    <- matched as Boolean: item contains \"admin\" and length item > 7\n  found <- cat users | filter by isMatch | head 1 | collect as List of String\n\nKEY INSIGHT\nStream filter + head for simple cases. Function decomposition with :=? for complex ones where you need transformation, context, or multi-step logic.\n\nSee Q145 for basic filter + head. See Q125 for head, tail, and skip. See Q89 for stream pipelines. See Q50 for unset return as signal.","ek9Example":"defines module qa.without.findmatch\n\n  defines function\n\n    isLong() as pure\n      -> item as String\n      <- long as Boolean?\n      minLongLength <- 5\n      long: length item > minLongLength\n\n    findAndTransform()\n      -> items as List of String\n      <- result as String: String()\n      minLongLength <- 5\n      for item in items\n        if length item > minLongLength and ~result?\n          result :=? item + \" (long)\"\n\n    findWithIndex()\n      ->\n        items as List of String\n        target as String\n      <- position as Integer: Integer()\n      idx <- 0\n      for item in items\n        if item == target and ~position?\n          position :=? idx\n        idx: idx + 1\n\n    containsAdmin() as pure\n      -> item as String\n      <- matched as Boolean?\n      minAdminLength <- 7\n      matched: item contains \"admin\" and length item > minAdminLength\n\n  defines program\n\n    FindFirstDemo()\n      stdout <- Stdout()\n\n      items <- [\"hi\", \"apple\", \"banana\", \"cherry\", \"date\", \"elderberry\"]\n\n      // === SIMPLE: FILTER + HEAD ===\n\n      firstLong <- cat items | filter by isLong | head 1 | collect as List of String\n      stdout.println(`First long item: ${firstLong}`)\n\n      // === SEARCH WITH TRANSFORMATION ===\n\n      transformed <- findAndTransform(items)\n      if transformed?\n        stdout.println(`Transformed: ${transformed}`)\n\n      // === SEARCH WITH INDEX ===\n\n      pos <- findWithIndex(items, \"cherry\")\n      if pos?\n        stdout.println(`Cherry found at index: ${pos}`)\n\n      notFoundPos <- findWithIndex(items, \"mango\")\n      if ~notFoundPos?\n        stdout.println(\"Mango not found in list\")\n\n      // === MULTI-FIELD SEARCH ===\n\n      users <- [\"user_joe\", \"admin_alice\", \"user_bob\", \"admin_charlie\"]\n      admins <- cat users | filter by containsAdmin | head 1 | collect as List of String\n      stdout.println(`First admin: ${admins}`)","migrationContext":"Java: for (item : list) { if (match) { result = item; break; } } or stream().filter().findFirst(). Python: for item in list: if match: result = item; break, or next(x for x in list if match, None). Rust: iter().find(|x| predicate). Go: for _, item := range list { if match { return item } }. Kotlin: list.firstOrNull { predicate }. EK9: cat items | filter by predicate | head 1 | collect, or function with :=? for complex search.","keywords":["alternative","early","filter","find","first","head","index","linear","lookup","match","no-break","no-return","search","stop","termination","transform"],"primaryTopics":[],"typicalErrors":[{"error":"E01070","correct":"position :=? idx","incorrect":"break","explanation":"EK9 has no break statement. To find the first match, use guard assignment (:=?) which only assigns when the variable is unset, replacing break-on-found. See ek9 -h E01070 for details."}],"companions":[]}
{"id":285,"category":"Control Flow Without break/continue/return","question":"How do I handle multiple preconditions without early return?","url":"https://ek9.io/qa/QA0285.html","alternatePhrasings":["How do I validate multiple conditions without return in EK9?","What replaces guard clauses with early return in EK9?","How do I write a validation chain without return statements in EK9?"],"answer":"In Java you write: if (!valid1) return error1; if (!valid2) return error2; doWork();. EK9 has no return. Use structured if/else chains or decompose into validator functions.\n\nTHE PROBLEM\nEarly return guards look clean in small functions but create hidden exits. Each return is a path the reader must trace. Cleanup code can be skipped. In large functions, the number of hidden exits becomes unmanageable.\n\nPATTERN 1: NESTED IF/ELSE CHAIN\nCheck each condition in sequence with all paths visible:\n  validate()\n    -> name as String, age as Integer, email as String\n    <- message as String: \"unknown error\"\n    if length name == 0\n      message: \"name is required\"\n    else if age < 0 or age > 150\n      message: \"age must be between 0 and 150\"\n    else if not (email contains \"@\")\n      message: \"email must contain @\"\n    else\n      message: \"valid\"\nEvery path is explicit. No hidden exits.\n\nPATTERN 2: VALIDATOR FUNCTIONS WITH GUARDS\nDecompose each check into its own function:\n  validateName() as pure\n    -> name as String\n    <- error as String: String()\n    if length name == 0\n      error: \"name is required\"\nThen compose with guarded assignment:\n  problem <- String()\n  problem :=? validateName(name)\n  problem :=? validateAge(age)\n  problem :=? validateEmail(email)\nThe :=? means the first error wins. Subsequent checks are still evaluated but their results are ignored once problem is set.\n\nPATTERN 3: VALIDATE THEN PROCESS\nSeparate validation from processing entirely:\n  isValid() as pure\n    -> name as String, age as Integer\n    <- valid as Boolean: length name > 0 and age >= 0 and age <= 150\nGuard on the result:\n  if isValid(name, age)\n    process(name, age)\n  else\n    reportError()\n\nKEY INSIGHT\nEarly return creates hidden exits. Explicit if/else makes ALL paths visible. Decomposition into validator functions keeps each check focused and testable.\n\nSee Q50 for declared returns. See Q274 for AI return patterns. See Q146 for decomposition. See Q74 for guard expressions.","ek9Example":"defines module qa.without.preconditions\n\n  defines function\n\n    // Pattern 1: Nested if/else chain\n    validateAll() as pure\n      ->\n        name as String\n        age as Integer\n        email as String\n      <- message as String: \"unknown error\"\n      maxAge <- 150\n      if length name == 0\n        message: \"name is required\"\n      else if age < 0 or age > maxAge\n        message: \"age must be between 0 and 150\"\n      else if not (email contains \"@\")\n        message: \"email must contain @\"\n      else\n        message: \"valid\"\n\n    // Pattern 2: Individual validators\n    validateName() as pure\n      -> name as String\n      <- error as String: String()\n      if length name == 0\n        error: \"name is required\"\n\n    validateAge() as pure\n      -> age as Integer\n      <- error as String: String()\n      maxAge <- 150\n      if age < 0 or age > maxAge\n        error: \"age must be between 0 and 150\"\n\n    validateEmail() as pure\n      -> email as String\n      <- error as String: String()\n      if not (email contains \"@\")\n        error: \"email must contain @\"\n\n    // Pattern 3: Boolean validator\n    isAllValid() as pure\n      ->\n        name as String\n        age as Integer\n      <- valid as Boolean?\n      maxAge <- 150\n      valid: length name > 0 and age >= 0 and age <= maxAge\n\n  defines program\n\n    PreconditionDemo()\n      stdout <- Stdout()\n\n      // === PATTERN 1: NESTED IF/ELSE ===\n\n      testName <- \"Alice\"\n      testEmail <- \"a@b.com\"\n\n      stdout.println(`Empty name: ${validateAll(\"\", 25, testEmail)}`)\n      stdout.println(`Bad age: ${validateAll(testName, -5, testEmail)}`)\n      stdout.println(`Bad email: ${validateAll(testName, 25, \"nope\")}`)\n      stdout.println(`All valid: ${validateAll(testName, 25, testEmail)}`)\n\n      // === PATTERN 2: COMPOSED VALIDATORS ===\n\n      problem <- String()\n      problem :=? validateName(testName)\n      problem :=? validateAge(200)\n      problem :=? validateEmail(testEmail)\n      if problem?\n        stdout.println(`Composed error: ${problem}`)\n\n      problem2 <- String()\n      problem2 :=? validateName(\"Bob\")\n      problem2 :=? validateAge(30)\n      problem2 :=? validateEmail(\"b@c.com\")\n      if ~problem2?\n        stdout.println(\"Composed: all valid\")\n\n      // === PATTERN 3: VALIDATE THEN PROCESS ===\n\n      if isAllValid(\"Charlie\", 40)\n        stdout.println(\"Charlie is valid, processing\")\n      else\n        stdout.println(\"Charlie is invalid\")\n\n      if isAllValid(\"\", 40)\n        stdout.println(\"Empty name is valid\")\n      else\n        stdout.println(\"Empty name rejected as expected\")","migrationContext":"Java: if (!valid) return error; guard clauses at top of method. Python: if not valid: return error early return guards. Rust: early return with ? operator for Result, pattern matching for validation. Go: if err := validate(); err != nil { return err } very common pattern. Kotlin: require() and check() for preconditions, early return. Swift: guard let for unwrapping, early return. EK9: no return, nested if/else chains, validator functions with :=? composition, validate-then-process decomposition.","keywords":["alternative","chain","check","condition","early","error","guard","isset","migrate","multiple","no-break","no-return","null-safe","precondition","return","safe","validate","validation"],"primaryTopics":[],"typicalErrors":[{"error":"E50001","correct":"validateName() as pure\n      -> name as String\n      <- error as String: String()\n      if length name == 0\n        error: \"name is required\"","incorrect":"validateName() as pure\n      -> name as String\n      <- error as String: String()\n      stdout.println(name)\n      if length name == 0\n        error: \"name is required\"","explanation":"A pure function cannot call non-pure methods like stdout.println(). Pure functions must have no side effects. See ek9 -h E50001 for details."}],"companions":[]}
{"id":286,"category":"Control Flow Without break/continue/return","question":"How do I accumulate partial results without break?","url":"https://ek9.io/qa/QA0286.html","alternatePhrasings":["How do I build a result from the first matching condition without break in EK9?","How does guarded assignment replace break-on-found in EK9?","How do I implement first-wins priority lookup in EK9?"],"answer":"In Java you write: result = null; for (...) { if (match) { result = x; break; } }. In EK9, the :=? guarded assignment operator is purpose-built for first-assignment-wins.\n\nTHE PROBLEM\nYou want to find a value from the first matching source in priority order. Other languages use a loop with break-on-found. The break is the mechanism for stopping after the first match.\n\nPATTERN 1: PRIORITY CHAIN WITH :=?\nTry each source in priority order. The first one that sets the value wins:\n  resolveConfig()\n    -> envSetting as String, fileSetting as String, hardcoded as String\n    <- setting as String: String()\n    setting :=? envSetting\n    setting :=? fileSetting\n    setting :=? hardcoded\nThe :=? operator only assigns when the target is unset. Once setting gets a value from envSetting, the subsequent assignments are no-ops. No loop, no break.\n\nPATTERN 2: STREAM COLLECT FOR BATCH\nWhen accumulating ALL matching items (not first-wins), use stream collect:\n  matches <- cat items | filter by isValid | collect as List of String\nNo break needed because you want everything that matches.\n\nPATTERN 3: SWITCH WITH DECLARED RETURN\nWhen dispatching on a single value, switch with a declared return replaces break-based dispatch:\n  classify() as pure\n    -> score as Integer\n    <- label as String: \"average\"\n    switch score\n      case >= 90\n        label: \"excellent\"\n      case >= 70\n        label: \"good\"\n      case < 40\n        label: \"poor\"\n      default\n        label: \"average\"\n\nKEY INSIGHT\nThe :=? operator IS the break-on-found replacement. It was designed specifically for the first-assignment-wins pattern. No loops needed for priority lookup. No break needed for accumulation.\n\nSee Q50 for guarded assignment. See Q29 for :=? semantics. See Q79 for guarded assignment details. See Q122 for collect.","ek9Example":"defines module qa.without.accumulate\n\n  defines function\n\n    // Pattern 1: Priority chain\n    resolveConfig()\n      ->\n        envSetting as String\n        fileSetting as String\n        hardcoded as String\n      <- setting as String: String()\n      setting :=? envSetting\n      setting :=? fileSetting\n      setting :=? hardcoded\n\n    isValid() as pure\n      -> item as String\n      <- valid as Boolean?\n      minValidLength <- 3\n      valid: length item > minValidLength\n\n    // Pattern 3: Switch with declared return\n    classify() as pure\n      -> score as Integer\n      <- label as String: \"average\"\n      switch score\n        case >= 90\n          label: \"excellent\"\n        case >= 70\n          label: \"good\"\n        case < 40\n          label: \"poor\"\n        default\n          label: \"average\"\n\n  defines program\n\n    AccumulateDemo()\n      stdout <- Stdout()\n\n      // === PATTERN 1: PRIORITY CHAIN ===\n\n      defaultSetting <- \"hardcoded\"\n\n      // Environment wins (first non-empty source)\n      envResult <- resolveConfig(\"from-env\", \"from-file\", defaultSetting)\n      stdout.println(`Priority (env set): ${envResult}`)\n\n      // File wins when env is unset\n      fileResult <- resolveConfig(String(), \"from-file\", defaultSetting)\n      stdout.println(`Priority (env unset): ${fileResult}`)\n\n      // Hardcoded wins when both are unset\n      defaultResult <- resolveConfig(String(), String(), defaultSetting)\n      stdout.println(`Priority (both unset): ${defaultResult}`)\n\n      // === PATTERN 2: STREAM COLLECT ===\n\n      items <- [\"hi\", \"apple\", \"banana\", \"ok\", \"cherry\"]\n      allValid <- cat items | filter by isValid | collect as List of String\n      stdout.println(`All valid items: ${allValid}`)\n\n      // === PATTERN 3: SWITCH DISPATCH ===\n\n      scores <- [95, 72, 55, 38]\n      for score in scores\n        stdout.println(`Score ${score}: ${classify(score)}`)\n\n      // === FIRST-WINS IN A LOOP ===\n\n      names <- [\"\", \"\", \"Alice\", \"Bob\"]\n      chosen <- String()\n      for name in names\n        if length name > 0\n          chosen :=? name\n      stdout.println(`First non-empty name: ${chosen}`)","migrationContext":"Java: result = null; for (...) { if (match) { result = x; break; } } or Optional.orElse chain. Python: result = None; for ...: if match: result = x; break, or next() with generator. Rust: iter().find() or match with first arm. Go: for loop with break on found. Kotlin: firstOrNull() or generateSequence. JavaScript: find() or for loop with break. EK9: :=? guarded assignment for priority chain, stream collect for batch, switch for dispatch.","keywords":["accumulate","alternative","assignment","build","collect","config","error","first","guarded","isset","lookup","migrate","no-break","no-return","null-safe","ok","partial","priority","result","safe","wins"],"primaryTopics":[],"typicalErrors":[{"error":"E50001","correct":"envResult <- resolveConfig(\"from-env\", \"from-file\", defaultSetting)","incorrect":"envResultXYZ <- resolveConfig(\"from-env\", \"from-file\", defaultSetting)","explanation":"Renaming the variable means later references to 'envResult' become unresolved, triggering E50001. Variable names must be consistent. See ek9 -h E50001 for details."}],"companions":[]}
{"id":287,"category":"Control Flow Without break/continue/return","question":"How do I process items until a condition is met without break?","url":"https://ek9.io/qa/QA0287.html","alternatePhrasings":["How do I replace while(true) with break-on-condition in EK9?","How do I consume items until done without break in EK9?","How do I write a processing loop that stops on a condition in EK9?"],"answer":"In Java you write: while(true) { item = next(); if (done(item)) break; process(item); }. EK9 replaces this with guard-driven termination: the while loop condition itself controls when to stop.\n\nTHE PROBLEM\nProcessing items until a termination condition is the most common use of while(true) with break. The break is buried inside the loop body, making the termination condition hard to find.\n\nPATTERN 1: WHILE WITH GUARD VARIABLE\nUse a Boolean flag that controls the while loop:\n  done <- Boolean(false)\n  idx <- 0\n  while ~done? and idx < length items\n    item <- items.getOrDefault(idx, \"\")\n    if item == \"STOP\"\n      done: true\n    else\n      process(item)\n    idx: idx + 1\nThe termination condition is in the while expression, not buried in the body.\n\nPATTERN 2: COUNTING WITH FOR-RANGE\nWhen processing up to a known maximum:\n  for count in 1 ... maxItems\n    if ~finished?\n      item <- getItem(count)\n      if isTerminal(item)\n        finished: true\n      else\n        process(item)\n\nPATTERN 3: STREAM WITH HEAD\nWhen you want the first N items that satisfy a condition:\n  results <- cat items | filter by isActive | head 10 | collect as List of String\nThe pipeline stops after 10 active items. No loop, no break, no counter.\n\nGUARD-DRIVEN VS BREAK-DRIVEN\nBreak-driven: the loop runs forever, break is the only way out. The termination logic is scattered through the body.\nGuard-driven: the while condition states exactly when the loop ends. The body focuses on processing.\n\nSee Q66 for while loops. See Q144 for no break. See Q125 for head.","ek9Example":"defines module qa.without.processuntil\n\n  defines function\n\n    isActive() as pure\n      -> item as String\n      <- active as Boolean: item <> \"STOP\" and length item > 0\n\n    processWithLimit()\n      ->\n        dataItems as List of String\n        readLimit as Integer\n      <- itemsRead as Integer: 0\n\n      for pos in 0 ... length dataItems - 1\n        if itemsRead < readLimit\n          itemsRead: itemsRead + 1\n\n  defines program\n\n    ProcessUntilDemo()\n      stdout <- Stdout()\n\n      // === PATTERN 1: WHILE WITH GUARD VARIABLE ===\n\n      items <- [\"alpha\", \"bravo\", \"charlie\", \"STOP\", \"delta\", \"echo\"]\n      done <- Boolean(false)\n      idx <- 0\n      processed <- 0\n      while ~done and idx < length items\n        item <- items.getOrDefault(idx, \"\")\n        if item == \"STOP\"\n          done: true\n        else\n          stdout.println(`Processing: ${item}`)\n          processed: processed + 1\n        idx: idx + 1\n      stdout.println(`Processed ${processed} items before STOP`)\n\n      // === PATTERN 2: COUNTING WITH FOR-RANGE ===\n\n      numbers <- [10, 20, 30, 40, 50, 60, 70]\n      runningTotal <- 0\n      totalLimit <- 100\n      exceeded <- Boolean(false)\n      for count in 0 ... length numbers - 1\n        if ~exceeded\n          currentNum <- numbers.getOrDefault(count, 0)\n          if runningTotal + currentNum > totalLimit\n            exceeded: true\n          else\n            runningTotal: runningTotal + currentNum\n      stdout.println(`Running total (stopped before exceeding 100): ${runningTotal}`)\n\n      // === PATTERN 3: STREAM WITH HEAD ===\n\n      allItems <- [\"\", \"alpha\", \"bravo\", \"\", \"charlie\", \"STOP\", \"delta\", \"echo\", \"foxtrot\"]\n      activeItems <- cat allItems | filter by isActive | head 3 | collect as List of String\n      stdout.println(`First 3 active items: ${activeItems}`)\n\n      // === BOUNDED PROCESSING WITH INDEX ===\n\n      dataItems <- [\"read1\", \"read2\", \"read3\", \"read4\", \"read5\"]\n      itemsRead <- processWithLimit(dataItems, 3)\n      stdout.println(`Bounded read processed ${itemsRead} items`)","migrationContext":"Java: while(true) { if (done) break; } or do-while with condition. Python: while True: if done: break. Rust: loop { if done { break; } } or while !done. Go: for { if done { break } }. Kotlin: while(true) { if (done) break }. JavaScript: while(true) { if (done) break; }. EK9: while with guard expression or flag variable, for-range for bounded processing, stream head for take-first-N.","keywords":["alternative","condition","consume","done","exhaust","guard","isset","loop","migrate","no-break","no-return","null-safe","process","safe","stop","terminate","until","while"],"primaryTopics":[],"typicalErrors":[{"error":"E01070","correct":"done: true","incorrect":"break","explanation":"EK9 has no break statement. To stop processing when a condition is met, set a guard flag variable and check it in the while condition. See ek9 -h E01070 for details."}],"companions":[]}
{"id":288,"category":"Control Flow Without break/continue/return","question":"How do I convert break-based loops from Java or Python to EK9?","url":"https://ek9.io/qa/QA0288.html","alternatePhrasings":["What are before and after examples for converting break loops to EK9?","How do I migrate a for loop with break to EK9 idioms?","What are the EK9 equivalents of common break loop patterns?"],"answer":"This shows four common break-based loop patterns and their EK9 equivalents side by side.\n\nSCENARIO 1: FIND FIRST X\nJava: for (String s : items) { if (s.length() > 5) { result = s; break; } }\nPython: for s in items: if len(s) > 5: result = s; break\nEK9 equivalent uses filter + head:\n  results <- cat items | filter by isLong | head 1 | collect as List of String\nThe pipeline stops after the first long item. No loop variable, no break.\n\nSCENARIO 2: PROCESS FIRST N MATCHING\nJava: int count = 0; for (String s : items) { if (isValid(s)) { process(s); if (++count >= 3) break; } }\nEK9 equivalent uses filter + head N:\n  topN <- cat items | filter by isValid | head 3 | collect as List of String\n  for item in topN\n    process(item)\nThe stream selects, the loop processes. Each part has one responsibility.\n\nSCENARIO 3: ACCUMULATE UNTIL LIMIT\nJava: int sum = 0; for (int n : numbers) { sum += n; if (sum > 100) break; }\nEK9 equivalent uses a guard flag:\n  total <- 0\n  exceeded <- Boolean(false)\n  for num in numbers\n    if ~exceeded\n      if total + num > 100\n        exceeded: true\n      else\n        total: total + num\nThe flag replaces break. The termination condition is checked at loop entry.\n\nSCENARIO 4: SKIP ITEMS THEN TAKE SOME\nJava: boolean started = false; int taken = 0; for (String s : items) { if (!started) { if (s.equals(\"START\")) started = true; continue; } process(s); if (++taken >= 2) break; }\nEK9 equivalent uses skip + head:\n  selected <- cat items | skip 2 | head 3 | collect as List of String\nFor conditional skip, use filter to remove unwanted prefix items.\n\nTHE PATTERN\nEvery break loop decomposes into: what do I want? Express the want as a stream pipeline or a guard-controlled loop.\n\nSee Q145 for replacing break and continue. See Q148 for migration rules. See Q275 for AI break patterns.","ek9Example":"defines module qa.without.migratebreak\n\n  defines function\n\n    isLong() as pure\n      -> item as String\n      <- long as Boolean?\n      minLongLength <- 5\n      long: length item > minLongLength\n\n    isValid() as pure\n      -> item as String\n      <- valid as Boolean?\n      minValidLength <- 2\n      valid: length item > minValidLength\n\n    formatItem() as pure\n      -> item as String\n      <- formatted as String: `[${item}]`\n\n  defines program\n\n    MigrateBreakDemo()\n      stdout <- Stdout()\n\n      items <- [\"hi\", \"apple\", \"banana\", \"fig\", \"cherry\", \"date\", \"elderberry\"]\n\n      // === SCENARIO 1: FIND FIRST LONG ITEM ===\n      // Java: for (s : items) { if (s.length() > 5) { result = s; break; } }\n\n      firstLong <- cat items | filter by isLong | head 1 | collect as List of String\n      stdout.println(`First long item: ${firstLong}`)\n\n      // === SCENARIO 2: PROCESS FIRST 3 VALID ===\n      // Java: count = 0; for (s : items) { if (valid(s)) { process(s); if (++count >= 3) break; } }\n\n      topThree <- cat items | filter by isValid | head 3 | collect as List of String\n      for item in topThree\n        stdout.println(`Processing valid: ${formatItem(item)}`)\n\n      // === SCENARIO 3: ACCUMULATE UNTIL LIMIT ===\n      // Java: sum = 0; for (n : numbers) { sum += n; if (sum > 100) break; }\n\n      numbers <- [10, 25, 30, 45, 50, 60]\n      total <- 0\n      totalLimit <- 100\n      exceeded <- Boolean(false)\n      for num in numbers\n        if ~exceeded\n          if total + num > totalLimit\n            exceeded: true\n          else\n            total: total + num\n      stdout.println(`Accumulated total (under 100): ${total}`)\n\n      // === SCENARIO 4: SKIP ITEMS THEN TAKE SOME ===\n      // Java: started = false; taken = 0; for (s : items) { if (!started) { continue; } process(s); if (++taken >= 2) break; }\n\n      selected <- cat items | skip 2 | head 3 | collect as List of String\n      stdout.println(`Skipped 2, took 3: ${selected}`)\n\n      // === ALL SCENARIOS COMBINED ===\n\n      fruits <- [\"fig\", \"apple\", \"banana\", \"cherry\", \"date\", \"elderberry\", \"grape\"]\n      longFruits <- cat fruits | filter by isLong | skip 1 | head 2 | collect as List of String\n      stdout.println(`Skip 1 long, take 2: ${longFruits}`)","migrationContext":"Java: for loop with break on condition, counter + break for first-N, accumulator + break on limit. Python: for with break, enumerate + break, itertools.islice as alternative. Rust: for with break, iter().take(n).filter(), iter().scan() for accumulation. Go: for with break, manual counter. Kotlin: for with break, take() and filter() on sequences. JavaScript: for with break, Array.find(), Array.some(). EK9: filter + head for selection, guard variables for accumulation, skip + head for windowing.","keywords":["after","alternative","before","break","convert","imperative","loop","migrate","migration","no-break","no-return","refactor","side","stream"],"primaryTopics":[],"typicalErrors":[{"error":"E01070","correct":"exceeded: true","incorrect":"break","explanation":"EK9 has no break statement. When migrating accumulate-until-limit loops from Java or Python, replace break with a Boolean flag checked in the loop condition. See ek9 -h E01070 for details."}],"companions":[]}
{"id":289,"category":"Control Flow Without break/continue/return","question":"How do I convert functions with multiple return statements to EK9?","url":"https://ek9.io/qa/QA0289.html","alternatePhrasings":["What are before and after examples for converting multiple returns to EK9?","How do I migrate early return guard clauses to EK9?","How do I replace ternary chains and multi-return functions in EK9?"],"answer":"This shows four common multi-return patterns and their EK9 equivalents side by side.\n\nSCENARIO 1: GUARD CLAUSE RETURNS\nJava: if (name == null || name.isEmpty()) return \"invalid\"; if (name.length() > 50) return \"too long\"; return \"ok\";\nEK9 equivalent uses if/else with declared return:\n  validateName() as pure\n    -> name as String\n    <- status as String: \"ok\"\n    if length name == 0\n      status: \"invalid\"\n    else if length name > 50\n      status: \"too long\"\nThe declared return starts with a default. Conditions modify it. Every path is visible.\n\nSCENARIO 2: LOOKUP WITH DEFAULT\nJava: for (String s : items) { if (s.equals(target)) return s; } return \"not found\";\nEK9 equivalent uses unset return + for loop + :=? default:\n  lookup()\n    -> items as List of String, target as String\n    <- found as String: String()\n    for item in items\n      if item == target and ~found?\n        found :=? item\nThe caller checks if found? and uses a default if not. No loop break, no return inside loop.\n\nSCENARIO 3: MULTI-BRANCH CLASSIFICATION\nJava: if (score >= 90) return \"A\"; if (score >= 80) return \"B\"; if (score >= 70) return \"C\"; return \"F\";\nEK9 equivalent uses declared return with conditional assignment:\n  grade() as pure\n    -> score as Integer\n    <- letter as String: \"F\"\n    if score >= 90\n      letter: \"A\"\n    else if score >= 80\n      letter: \"B\"\n    else if score >= 70\n      letter: \"C\"\n\nSCENARIO 4: TERNARY CHAIN\nJava: return a > 0 ? \"positive\" : a < 0 ? \"negative\" : \"zero\";\nEK9 equivalent uses :=? chain:\n  describe() as pure\n    -> num as Integer\n    <- label as String?\n    if num > 0\n      label :=? \"positive\"\n    else if num < 0\n      label :=? \"negative\"\n    else\n      label :=? \"zero\"\nEach branch assigns exactly once via :=?. The compiler verifies all paths set the value.\n\nTHE PATTERN\nEvery multi-return function becomes: declare the return variable with a sensible default, then use if/else to modify it. The compiler verifies every path initialises the return.\n\nSee Q50 for declared returns. See Q146 for decomposition. See Q274 for AI return patterns. See Q148 for migration.","ek9Example":"defines module qa.without.migratereturn\n\n  defines function\n\n    // Scenario 1: Guard clause returns\n    validateName() as pure\n      -> name as String\n      <- status as String: \"ok\"\n      maxNameLength <- 50\n      if length name == 0\n        status: \"invalid\"\n      else if length name > maxNameLength\n        status: \"too long\"\n\n    // Scenario 2: Lookup with default\n    lookup()\n      ->\n        items as List of String\n        target as String\n      <- found as String: String()\n      for item in items\n        if item == target and ~found?\n          found :=? item\n\n    // Scenario 3: Multi-branch classification\n    grade() as pure\n      -> score as Integer\n      <- letter as String: \"F\"\n      excellentMinScore <- 90\n      goodMinScore <- 80\n      averageMinScore <- 70\n      if score >= excellentMinScore\n        letter: \"A\"\n      else if score >= goodMinScore\n        letter: \"B\"\n      else if score >= averageMinScore\n        letter: \"C\"\n\n    // Scenario 4: Ternary chain\n    describeSign() as pure\n      -> num as Integer\n      <- label as String?\n      if num > 0\n        label :=? \"positive\"\n      else if num < 0\n        label :=? \"negative\"\n      else\n        label :=? \"zero\"\n\n  defines program\n\n    MigrateReturnDemo()\n      stdout <- Stdout()\n\n      // === SCENARIO 1: GUARD CLAUSE RETURNS ===\n\n      stdout.println(`Empty name: ${validateName(\"\")}`)\n      stdout.println(`Long name: ${validateName(\"abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz\")}`)\n      stdout.println(`Valid name: ${validateName(\"Alice\")}`)\n\n      // === SCENARIO 2: LOOKUP WITH DEFAULT ===\n\n      fruits <- [\"apple\", \"banana\", \"cherry\"]\n      if result <- lookup(fruits, \"banana\")\n        stdout.println(`Found: ${result}`)\n\n      missing <- lookup(fruits, \"mango\")\n      if ~missing?\n        stdout.println(\"Mango not found, using default\")\n\n      // === SCENARIO 3: MULTI-BRANCH CLASSIFICATION ===\n\n      scores <- [95, 85, 75, 55]\n      for score in scores\n        stdout.println(`Score ${score}: grade ${grade(score)}`)\n\n      // === SCENARIO 4: TERNARY CHAIN ===\n\n      numbers <- [42, -7, 0]\n      for num in numbers\n        stdout.println(`${num} is ${describeSign(num)}`)","migrationContext":"Java: return in every branch, ternary operator for simple cases, guard clauses at top. Python: return in each branch, or pattern for defaults. Rust: implicit return from last expression in each branch, or early return with ?. Go: return in each branch, error return pattern. Kotlin: return or when expression. Swift: return or switch expression. EK9: declared return variable, if/else modifies it, :=? for pure functions, compiler verifies all paths.","keywords":["after","alternative","before","classification","convert","declared","function","guard","isset","migrate","multiple","named","no-break","no-return","null-safe","refactor","return","safe","ternary"],"primaryTopics":[],"typicalErrors":[{"error":"E50001","correct":"grade() as pure\n      -> score as Integer\n      <- letter as String: \"F\"","incorrect":"grade() as pure\n      -> score as Integer\n      <- letter as String: \"F\"\n      stdout.println(score)","explanation":"A pure function cannot call non-pure methods like stdout.println(). Pure functions must be free of side effects. See ek9 -h E50001 for details."}],"companions":[]}
{"id":290,"category":"Variable Naming Rules and Conventions","question":"What variable names are banned in EK9?","url":"https://ek9.io/qa/QA0290.html","alternatePhrasings":["Which identifiers are forbidden as variable names in EK9?","What names trigger E11031 or E11032 in EK9?","What are the reserved and restricted variable names in EK9?"],"answer":"EK9 enforces naming quality at compile time across four tiers of restriction. No other mainstream language does this. Every banned name has a specific reason and error code.\n\nTIER 1: NON-DESCRIPTIVE NAMES (E11031)\nThe following names are rejected because they carry no semantic meaning: temp, tmp, flag, flg, data, dat, object, obj, value, val, buffer, buf. The check is case-insensitive, so Data, DATA, and data are all rejected. Research shows these names correlate with 2.7x higher defect density.\n\nOPERATOR KEYWORDS (E11032)\nThe following names shadow built-in operators and are rejected: empty, length, contains, abs, sqrt, close, matches. Using these as variable names would make the corresponding operator unusable in scope.\n\nRESERVED WORDS (E01060)\nThe following are reserved for the EK9 testing framework: assert, require, assertThrows, assertDoesNotThrow. These are not available as variable or type names.\n\nEXCLUDED KEYWORDS (E01070-E01080)\nKeywords from other languages that do not exist in EK9: break, continue, return, null, goto, new, def, elif, None, self. These are excluded by design to prevent confusion when migrating from other languages.\n\nSINGLE-CHARACTER EXEMPTION\nSingle-character names are always allowed: x, y, z, i, j, k, T, K, V. The compiler trusts these as intentional shorthand for mathematical variables, loop counters, and generic type parameters.\n\nSee Q291 for troubleshooting rejected names. See Q296 for research evidence behind these bans. See Q297 for a renaming guide. See Q22 for variable declarations. See Q248 for error code lookup. See Q311 for full quality checks catalog.","ek9Example":"defines module qa.naming.banned\n\n  defines function\n\n    calculateArea() as pure\n      ->\n        width as Float\n        height as Float\n      <- area as Float: width * height\n\n    formatGreeting() as pure\n      -> customerName as String\n      <- greeting as String: \"Hello, \" + customerName\n\n    isRetryNeeded() as pure\n      -> attemptCount as Integer\n      <- shouldRetry as Boolean?\n\n      maxAttempts <- 3\n      shouldRetry :=? attemptCount < maxAttempts\n\n  defines program\n\n    BannedNamesDemo()\n      stdout <- Stdout()\n\n      // === SINGLE-CHARACTER NAMES: ALWAYS ALLOWED ===\n\n      x <- 10.0\n      y <- 20.0\n      z <- calculateArea(x, y)\n      stdout.println(`Area: ${z}`)\n\n      // === DESCRIPTIVE CAMELCASE: ALWAYS ALLOWED ===\n\n      customerName <- \"Alice\"\n      orderCount <- 5\n      isActive <- true\n      stdout.println(`Customer: ${customerName}, Orders: ${orderCount}, Active: ${isActive}`)\n\n      // === COMPOUND NAMES: ALWAYS ALLOWED ===\n\n      totalPrice <- 99.95\n      retryAttempt <- 1\n      outputMessage <- formatGreeting(customerName)\n      stdout.println(`${outputMessage}, Price: ${totalPrice}`)\n\n      // === GOOD ALTERNATIVES TO BANNED NAMES ===\n\n      swapHolder <- x\n      isComplete <- true\n      sensorReading <- 42.5\n      currentItem <- \"widget\"\n      computedScore <- 88\n      readChunk <- \"packet contents\"\n      stdout.println(`Swap: ${swapHolder}, Done: ${isComplete}`)\n      stdout.println(`Sensor: ${sensorReading}, Item: ${currentItem}`)\n      stdout.println(`Score: ${computedScore}, Chunk: ${readChunk}`)\n\n      // === GOOD ALTERNATIVES TO OPERATOR KEYWORD NAMES ===\n\n      isBlank <- length customerName == 0\n      nameLength <- length customerName\n      targetItem <- 2\n      hasItem <- [1, 2, 3] contains targetItem\n      positiveAmount <- 42\n      needsRetry <- isRetryNeeded(retryAttempt)\n      stdout.println(`Blank: ${isBlank}, Length: ${nameLength}`)\n      stdout.println(`Has item: ${hasItem}, Amount: ${positiveAmount}`)\n      stdout.println(`Retry: ${needsRetry}`)","migrationContext":"Java: allows any valid identifier, relies on linters like Checkstyle or SonarQube for naming rules. Python: allows any valid identifier, PEP 8 naming conventions are voluntary. Rust: allows any identifier, clippy provides optional naming warnings. Go: allows any identifier, golint suggests naming conventions. C/C++: allows any non-keyword identifier, no naming enforcement. Kotlin: allows any identifier, detekt provides optional rules. JavaScript: allows any valid identifier, ESLint rules are optional. EK9: compiler enforces naming quality with four tiers of restriction, no configuration needed, cannot be disabled.","keywords":["E01060","E11031","E11032","banned","compile","convention","error","forbidden","identifier","migrate","name","naming","reserved","restricted","variable"],"primaryTopics":["banned names","reserved names","naming rules"],"typicalErrors":[{"error":"E11031","correct":"sensorReading","incorrect":"data","explanation":"The name 'data' is non-descriptive and banned by the compiler. Use a name that describes what the variable represents, such as 'sensorReading'. See ek9 -h E11031 for details."},{"error":"E11032","correct":"isBlank","incorrect":"empty","explanation":"The name 'empty' shadows a built-in operator keyword and is rejected. Use a descriptive alternative like 'isBlank'. See ek9 -h E11032 for details."},{"error":"E11031","correct":"computedScore","incorrect":"value","explanation":"The name 'value' is non-descriptive and banned by the compiler. Use a name that communicates what the value represents. See ek9 -h E11031 for details."}],"companions":[]}
{"id":291,"category":"Variable Naming Rules and Conventions","question":"Why does the compiler reject my variable name?","url":"https://ek9.io/qa/QA0291.html","alternatePhrasings":["How do I fix E11031 non-descriptive variable name error?","How do I fix E11032 operator keyword as variable name error?","What should I rename my rejected variable to in EK9?"],"answer":"EK9 rejects variable names that are non-descriptive or shadow operators. Each error code tells you exactly what category of restriction you hit.\n\nE11031: NON-DESCRIPTIVE VARIABLE NAME\nYou used one of: temp, tmp, flag, flg, data, dat, object, obj, value, val, buffer, buf. The check is case-insensitive. Fix: describe what the variable represents. Instead of temp use swapHolder or intermediateResult. Instead of flag use isComplete or shouldRetry. Instead of data use customerRecord or sensorReading. Instead of value use totalPrice or measuredWeight. Instead of buffer use outputBuilder or readChunk.\n\nE11032: OPERATOR KEYWORD AS VARIABLE NAME\nYou used one of: empty, length, contains, abs, sqrt, close, matches. These shadow built-in operators. Fix: add context. Instead of empty use isBlank or emptyBasket. Instead of length use nameLength or pathSize. Instead of contains use hasItem or includesKey. Instead of matches use foundItems or searchHits. Instead of abs use absoluteDistance. Instead of close use closeFile or shutdownConnection.\n\nE01060: RESERVED WORD AS IDENTIFIER\nYou used assert, require, assertThrows, or assertDoesNotThrow. These are reserved for the testing framework. Fix: use testResult, precondition, or validationCheck instead.\n\nE01070-E01080: EXCLUDED KEYWORD\nYou used break, continue, return, null, goto, new, def, elif, None, or self. These keywords do not exist in EK9. Fix: these are not just naming issues, they indicate you are thinking in another language. See Q144 for why EK9 has no break, continue, or return.\n\nQUICK FIX TABLE\n  temp/tmp -> swapHolder, intermediateResult, pendingEntry\n  flag/flg -> isComplete, hasPermission, shouldRetry\n  data/dat -> customerRecord, sensorReading, responsePayload\n  object/obj -> currentItem, targetEntity, parsedElement\n  value/val -> totalPrice, measuredWeight, userInput\n  buffer/buf -> outputBuilder, messageAccumulator, readChunk\n  empty -> isBlank, emptyBasket, noResults\n  length -> nameLength, pathSize, messageCount\n  contains -> hasItem, includesKey, foundMatch\n  matches -> foundItems, matchingRecords, searchHits\n\nSee Q290 for the complete banned list. See Q297 for a comprehensive renaming guide. See Q248 for all error codes.","ek9Example":"defines module qa.naming.troubleshoot\n\n  defines function\n\n    processOrder() as pure\n      ->\n        orderTotal as Float\n        customerName as String\n      <- receiptMessage as String: `Order for ${customerName}: ${orderTotal}`\n\n    calculateDiscount() as pure\n      ->\n        originalPrice as Float\n        discountRate as Float\n      <- discountedPrice as Float: originalPrice * (1.0 - discountRate)\n\n    retryUntilDone()\n      -> maxAttempts as Integer\n      <- attemptCount as Integer: 0\n\n      isRetryNeeded <- Boolean(true)\n      while isRetryNeeded and attemptCount < maxAttempts\n        attemptCount++\n        if attemptCount >= maxAttempts\n          isRetryNeeded: false\n\n  defines program\n\n    TroubleshootNamingDemo()\n      stdout <- Stdout()\n\n      // === FIXED E11031: temp -> swapHolder ===\n\n      firstNumber <- 10\n      secondNumber <- 20\n      swapHolder <- firstNumber\n      firstNumber := secondNumber\n      secondNumber := swapHolder\n      stdout.println(`After swap: ${firstNumber}, ${secondNumber}`)\n\n      // === FIXED E11031: flag -> isRetryNeeded ===\n\n      attemptCount <- retryUntilDone(3)\n      stdout.println(`Attempts: ${attemptCount}`)\n\n      // === FIXED E11031: data -> customerRecord ===\n\n      customerRecord <- \"Alice:Premium:2024\"\n      stdout.println(`Record: ${customerRecord}`)\n\n      // === FIXED E11032: empty -> emptyBasket ===\n\n      emptyBasket <- List() of String\n      stdout.println(`Empty basket: ${emptyBasket}`)\n\n      // === FIXED E11032: length -> nameLength ===\n\n      greeting <- \"Hello, World\"\n      nameLength <- length greeting\n      stdout.println(`Name length: ${nameLength}`)\n\n      // === COMBINED EXAMPLE: GOOD NAMING THROUGHOUT ===\n\n      originalPrice <- 100.0\n      discountRate <- 0.15\n      finalPrice <- calculateDiscount(originalPrice, discountRate)\n      receiptMessage <- processOrder(finalPrice, \"Alice\")\n      stdout.println(receiptMessage)","migrationContext":"Java: no compile-time naming enforcement, Checkstyle rules are optional and configurable. Python: no naming enforcement, PEP 8 is advisory. Rust: no naming enforcement beyond snake_case warnings. Go: no naming enforcement, golint is advisory. C/C++: no naming enforcement. Kotlin: no naming enforcement. JavaScript: ESLint naming rules are optional. EK9: compiler enforces naming at compile time, cannot be disabled, four tiers of restrictions with specific error codes.","keywords":["E01060","E11031","E11032","banned","compile","compiler","convention","error","fix","identifier","naming","reject","rename","troubleshoot","variable","why"],"primaryTopics":[],"typicalErrors":[{"error":"E11031","correct":"swapHolder","incorrect":"temp","explanation":"The name 'temp' is non-descriptive and banned by the compiler. Use 'swapHolder' or another name that describes the variable's purpose. See ek9 -h E11031 for details."},{"error":"E11032","correct":"nameLength","incorrect":"length","explanation":"The name 'length' shadows a built-in operator keyword and is rejected. Use a descriptive alternative like 'nameLength'. See ek9 -h E11032 for details."},{"error":"E11031","correct":"customerRecord","incorrect":"data","explanation":"The name 'data' is non-descriptive and banned. Use a name like 'customerRecord' that describes what the data represents. See ek9 -h E11031 for details."}],"companions":[]}
{"id":292,"category":"Variable Naming Rules and Conventions","question":"What are the naming conventions for variables in EK9?","url":"https://ek9.io/qa/QA0292.html","alternatePhrasings":["How should I name variables in EK9?","What style should variable names follow in EK9?","What naming pattern does EK9 use for fields and parameters?"],"answer":"EK9 uses camelCase for all variable names: local variables, fields, parameters, and return values. The compiler enforces descriptive naming, so every variable name should communicate its purpose.\n\nLOCAL VARIABLES\nUse camelCase with descriptive intent: customerName, orderCount, isActive, totalPrice. Infer types with <- for concise declarations. Avoid abbreviations: use customer not cust, message not msg.\n\nFIELDS\nSame as local variables. Fields are private by default and must be initialised inline: greeting as String: \"Hello\". Use descriptive names that describe the data, not the type: accountBalance not floatBalance.\n\nPARAMETERS\nParameters require explicit types. Use names that describe what the parameter represents: -> firstName as String, not -> s as String. For multiple parameters, each on its own line under ->.\n\nRETURN VALUES\nReturn values are named and describe the output: <- formattedName as String. The name documents what the function produces. Choose names like calculatedTotal, validatedInput, formattedOutput.\n\nBOOLEAN NAMING\nUse is/has/can/should prefixes: isActive, hasPermission, canEdit, shouldRetry. These make conditions read naturally: if isActive, while hasPermission.\n\nCOLLECTION NAMING\nUse plural nouns: customers, orderItems, errorMessages. This distinguishes collections from single items and makes for-in loops read naturally: for customer in customers.\n\nAVOID\nAbbreviations (use customer not cust), Hungarian notation (not strName), type-in-name (not nameString), generic names (not item1, item2).\n\nSee Q22 for variable declarations. See Q293 for type naming. See Q294 for function naming.","ek9Example":"defines module qa.naming.variables\n\n  defines class\n\n    CustomerOrder\n      customerName as String: String()\n      orderItems as List of String: List() of String\n      totalPrice as Float: 0.0\n      isComplete as Boolean: false\n\n      CustomerOrder()\n        ->\n          name as String\n          items as List of String\n          price as Float\n        this.customerName :=: name\n        this.orderItems :=: items\n        this.totalPrice :=: price\n\n      getCustomerName() as pure\n        <- rtn as String: customerName\n\n      getOrderItems() as pure\n        <- rtn as List of String: orderItems\n\n      getTotalPrice() as pure\n        <- rtn as Float: totalPrice\n\n      markComplete()\n        isComplete: true\n\n      hasItems() as pure\n        <- rtn as Boolean: length orderItems > 0\n\n      operator $ as pure\n        <- rtn as String: `Order(${customerName}, ${length orderItems} items, ${totalPrice})`\n\n      override operator ? as pure\n        <- rtn as Boolean: customerName? and totalPrice?\n\n  defines function\n\n    formatOrderSummary() as pure\n      ->\n        customerName as String\n        itemCount as Integer\n        totalPrice as Float\n      <- formattedSummary as String: `${customerName}: ${itemCount} items, total ${totalPrice}`\n\n    isHighValueOrder() as pure\n      -> orderTotal as Float\n      <- isHighValue as Boolean?\n\n      highValueThreshold <- 500.0\n      isHighValue :=? orderTotal > highValueThreshold\n\n  defines program\n\n    VariableConventionsDemo()\n      stdout <- Stdout()\n\n      // === LOCAL VARIABLES: camelCase, descriptive ===\n\n      customerName <- \"Alice\"\n      orderCount <- 3\n      isActive <- true\n      totalPrice <- 299.95\n      stdout.println(`Customer: ${customerName}, Orders: ${orderCount}`)\n      stdout.println(`Active: ${isActive}, Total: ${totalPrice}`)\n\n      // === BOOLEAN NAMING: is/has/can/should prefixes ===\n\n      hasPermission <- true\n      canEdit <- isActive and hasPermission\n      maxRetries <- 5\n      shouldRetry <- orderCount < maxRetries\n      stdout.println(`Can edit: ${canEdit}, Should retry: ${shouldRetry}`)\n\n      // === COLLECTION NAMING: plural nouns ===\n\n      orderItems <- [\"Widget\", \"Gadget\", \"Sprocket\"]\n      errorMessages <- List() of String\n      stdout.println(`Items: ${orderItems}`)\n      stdout.println(`Errors: ${errorMessages}`)\n\n      // === FIELDS AND METHODS: descriptive throughout ===\n\n      order <- CustomerOrder(customerName, orderItems, totalPrice)\n      stdout.println($order)\n      stdout.println(`Has items: ${order.hasItems()}`)\n\n      // === RETURN VALUES: describe the output ===\n\n      formattedSummary <- formatOrderSummary(customerName, length orderItems, totalPrice)\n      stdout.println(formattedSummary)\n\n      // === HIGH VALUE CHECK ===\n\n      isExpensive <- isHighValueOrder(totalPrice)\n      stdout.println(`High value: ${isExpensive}`)","migrationContext":"Java: camelCase by convention (not enforced), fields often prefixed with m_ in Android. Python: snake_case for variables and functions (PEP 8), not enforced. Rust: snake_case for variables and functions (enforced as warning). Go: camelCase, exported names start with uppercase. C/C++: no standard, varies by project (camelCase, snake_case, Hungarian). Kotlin: camelCase by convention, similar to Java. JavaScript: camelCase by convention, not enforced. Swift: camelCase for variables. EK9: camelCase enforced by convention, descriptive naming enforced by compiler, no abbreviations or generic names allowed.","keywords":["boolean","camelCase","collection","convention","descriptive","field","identifier","naming","parameter","pattern","style","variable"],"primaryTopics":["naming convention","variable naming","code style"],"typicalErrors":[{"error":"E11031","correct":"hasPermission <- true\n      canEdit <- isActive and hasPermission","incorrect":"obj <- true\n      canEdit <- isActive and obj","explanation":"The name 'obj' is non-descriptive and banned by the compiler. Use a name that describes what the variable represents. See ek9 -h E11031 for details."},{"error":"E08090","correct":"stdout.println(`Can edit: ${canEdit}, Should retry: ${shouldRetry}`)","incorrect":"editSummary <- `Can edit: ${canEdit}, Should retry: ${shouldRetry}`","explanation":"If 'canEdit' is declared but never used in any output or expression, the compiler rejects it as an unused variable. See ek9 -h E50050 for details."}],"companions":[]}
{"id":293,"category":"Variable Naming Rules and Conventions","question":"What are the naming conventions for types in EK9?","url":"https://ek9.io/qa/QA0293.html","alternatePhrasings":["How should I name classes, records, and traits in EK9?","What naming style do EK9 types use?","How do I name modules and generic type parameters in EK9?"],"answer":"EK9 uses PascalCase for all type names: classes, records, traits, enumerations, and functions used as types. Module names use lowercase dot-separated segments.\n\nCLASSES\nPascalCase nouns describing what the class represents: CustomerOrder, PaymentProcessor, EmailValidator. Classes model entities or processors.\n\nRECORDS\nPascalCase nouns describing the data structure: Coordinate, Temperature, HttpResponse. Records are data-focused types with public fields, constructors, and operators (but no methods). Records are mutable — use 'as pure' to control mutation.\n\nTRAITS\nPascalCase adjectives or capability names: Printable, Sortable, Configurable. Traits describe what a type can do, not what it is.\n\nENUMERATIONS\nPascalCase singular nouns: Colour, DayOfWeek, HttpStatus. Each value represents one instance of the concept.\n\nFUNCTIONS AS TYPES\nPascalCase verb phrases when used as abstract function types: FormatName, CalculateTotal, ValidateInput. Standalone functions use camelCase (see Q294).\n\nGENERIC TYPE PARAMETERS\nSingle uppercase letters: T for general type, K for key, V for value, E for element. These are universally understood conventions.\n\nMODULES\nLowercase dot-separated: com.example.banking, org.ek9.core. Module names follow reverse domain convention.\n\nCONSTRAINED TYPES\nNamed for the constraint they represent: EmailAddress as String constrain, PositiveInteger as Integer constrain. The name describes what the constraint enforces.\n\nSee Q292 for variable naming. See Q294 for function naming. See Q93 for classes. See Q97 for records.","ek9Example":"defines module qa.naming.types\n\n  defines trait\n\n    Printable\n      printTo() as pure abstract\n        -> target as String\n        <- formattedOutput as String?\n\n  defines record\n\n    Coordinate\n      xPosition as Float: 0.0\n      yPosition as Float: 0.0\n\n      Coordinate()\n        ->\n          initialX as Float\n          initialY as Float\n        this.xPosition :=: initialX\n        this.yPosition :=: initialY\n\n      operator $ as pure\n        <- rtn as String: `(${xPosition}, ${yPosition})`\n\n      operator <=> as pure\n        -> other as Coordinate\n        <- rtn as Integer: xPosition <=> other.xPosition\n\n      override operator ? as pure\n        <- rtn as Boolean: xPosition? and yPosition?\n\n  defines class\n\n    ShapeRenderer with trait of Printable\n      shapeName as String: String()\n\n      ShapeRenderer()\n        -> name as String\n        this.shapeName :=: name\n\n      override printTo() as pure\n        -> target as String\n        <- formattedOutput as String: `${shapeName} -> ${target}`\n\n      operator $ as pure\n        <- rtn as String: `Renderer(${shapeName})`\n\n      override operator ? as pure\n        <- rtn as Boolean: shapeName?\n\n  defines type\n\n    DayOfWeek\n      Monday\n      Tuesday\n      Wednesday\n      Thursday\n      Friday\n      Saturday\n      Sunday\n\n  defines function\n\n    FormatCoordinate() as pure abstract\n      -> point as Coordinate\n      <- formattedPoint as String?\n\n  defines program\n\n    TypeConventionsDemo()\n      stdout <- Stdout()\n\n      // === RECORD: PascalCase noun ===\n\n      origin <- Coordinate(0.0, 0.0)\n      destination <- Coordinate(3.5, 7.2)\n      stdout.println(`Origin: ${origin}`)\n      stdout.println(`Destination: ${destination}`)\n\n      // === CLASS WITH TRAIT: PascalCase ===\n\n      renderer <- ShapeRenderer(\"Circle\")\n      renderedOutput <- renderer.printTo(\"Canvas\")\n      stdout.println(renderedOutput)\n\n      // === ENUMERATION: PascalCase singular ===\n\n      today <- DayOfWeek.Wednesday\n      stdout.println(`Today: ${today}`)\n\n      // === GENERIC TYPE PARAMETER CONVENTION ===\n\n      names <- [\"Alice\", \"Bob\", \"Charlie\"]\n      for n in names\n        stdout.println(`Name: ${n}`)\n\n      // === FUNCTION TYPE: PascalCase verb phrase ===\n\n      formatter <- (capturedOrigin: origin) is FormatCoordinate as pure function\n        formattedPoint :=? `Point at ${point} from ${capturedOrigin}`\n\n      stdout.println(formatter(destination))","migrationContext":"Java: PascalCase for classes (convention, not enforced), package names lowercase. Python: PascalCase for classes (PEP 8, not enforced), snake_case for modules. Rust: PascalCase for types (enforced as warning), snake_case for modules. Go: PascalCase for exported types, lowercase for unexported. C++: no standard, varies widely. Kotlin: PascalCase for classes (convention), package names lowercase. Swift: PascalCase for types (convention). EK9: PascalCase for all types, lowercase dot-separated for modules, single uppercase for generics, descriptive naming enforced by compiler.","keywords":["PascalCase","class","constrained","convention","enumeration","function","generic","identifier","module","naming","record","trait","type"],"primaryTopics":[],"typicalErrors":[{"error":"E50060","correct":"renderedOutput <- renderer.printTo(\"Canvas\")","incorrect":"renderedOutput <- renderer.render(\"Canvas\")","explanation":"The method is named 'printTo' not 'render'. Calling a method that does not exist on the type produces a resolution error. See ek9 -h E50060 for details."},{"error":"E08090","correct":"stdout.println(`Today: ${today}`)","incorrect":"todaySummary <- `Today: ${today}`","explanation":"If the variable 'today' is declared but never referenced in any expression, the compiler rejects it as unused. See ek9 -h E50050 for details."}],"companions":[]}
{"id":294,"category":"Variable Naming Rules and Conventions","question":"What are the naming conventions for functions in EK9?","url":"https://ek9.io/qa/QA0294.html","alternatePhrasings":["How should I name functions and methods in EK9?","What naming style do EK9 programs and services use?","How do I name standalone functions versus methods in EK9?"],"answer":"EK9 uses camelCase for standalone functions and methods, and PascalCase for programs, services, and abstract function types.\n\nSTANDALONE FUNCTIONS\ncamelCase, verb-first when performing an action: calculateTotal, formatDate, validateEmail. The name describes what the function does.\n\nABSTRACT FUNCTIONS\nPascalCase because they serve as type contracts: Predicate, Comparator, Transformer, FormatName. Abstract functions define the signature that concrete implementations must follow.\n\nMETHODS\ncamelCase, verb-first: getName, setStatus, calculateDiscount, processPayment. Methods describe operations on the object.\n\nPURE FUNCTIONS\nSame camelCase naming. The 'as pure' modifier communicates purity; the name communicates intent. Use verbs that suggest computation: compute, calculate, format, validate, derive.\n\nOPERATORS\nUse standard operator symbols, not names: operator +, operator ==, operator $. Operator implementations do not need naming conventions since they follow the fixed operator set.\n\nPROGRAMS\nPascalCase nouns: HelloWorld, InventoryManager, DataMigrator. Programs are entry points and named like types.\n\nSERVICES\nPascalCase nouns: UserService, AuthenticationService, OrderProcessor. Services define REST endpoints and are named for their domain.\n\nSee Q292 for variable naming. See Q293 for type naming. See Q49 for function basics. See Q96 for operator overloading.","ek9Example":"defines module qa.naming.functions\n\n  defines function\n\n    calculateTotal() as pure\n      ->\n        unitPrice as Float\n        quantity as Integer\n      <- totalPrice as Float: unitPrice * Float(quantity)\n\n    formatCurrency() as pure\n      -> amount as Float\n      <- formattedAmount as String: \"USD \" + $amount\n\n    isEligibleForDiscount() as pure\n      -> orderTotal as Float\n      <- isEligible as Boolean?\n\n      discountThreshold <- 100.0\n      isEligible :=? orderTotal > discountThreshold\n\n    ValidateInput() as pure abstract\n      -> userInput as String\n      <- isValid as Boolean?\n\n  defines class\n\n    OrderProcessor\n      orderName as String: String()\n      orderTotal as Float: 0.0\n\n      OrderProcessor()\n        ->\n          name as String\n          total as Float\n        this.orderName :=: name\n        this.orderTotal :=: total\n\n      getOrderName() as pure\n        <- rtn as String: orderName\n\n      getOrderTotal() as pure\n        <- rtn as Float: orderTotal\n\n      applyDiscount()\n        -> discountRate as Float\n        orderTotal: orderTotal * (1.0 - discountRate)\n\n      formatReceipt() as pure\n        <- receipt as String: `Receipt: ${orderName} - ${formatCurrency(orderTotal)}`\n\n      operator $ as pure\n        <- rtn as String: `Order(${orderName}, ${orderTotal})`\n\n      override operator ? as pure\n        <- rtn as Boolean: orderName? and orderTotal?\n\n  defines program\n\n    FunctionConventionsDemo()\n      stdout <- Stdout()\n\n      // === STANDALONE FUNCTIONS: camelCase, verb-first ===\n\n      totalPrice <- calculateTotal(29.99, 3)\n      formattedPrice <- formatCurrency(totalPrice)\n      stdout.println(`Total: ${formattedPrice}`)\n\n      // === PURE FUNCTION: same camelCase ===\n\n      isEligible <- isEligibleForDiscount(totalPrice)\n      stdout.println(`Eligible for discount: ${isEligible}`)\n\n      // === METHODS: camelCase, verb-first ===\n\n      processor <- OrderProcessor(\"Laptop Bundle\", totalPrice)\n      stdout.println(processor.formatReceipt())\n      processor.applyDiscount(0.10)\n      stdout.println(processor.formatReceipt())\n\n      // === ABSTRACT FUNCTION TYPE: PascalCase ===\n\n      emailValidator <- () is ValidateInput as pure function\n        isValid :=? userInput contains \"@\"\n\n      testEmail <- \"alice@example.com\"\n      stdout.println(`Valid email: ${emailValidator(testEmail)}`)","migrationContext":"Java: camelCase for methods (convention), PascalCase for classes, no standalone functions. Python: snake_case for functions and methods (PEP 8). Rust: snake_case for functions (enforced as warning). Go: camelCase for functions, PascalCase for exported. C++: no standard naming convention. Kotlin: camelCase for functions, PascalCase for classes. Swift: camelCase for functions and methods. JavaScript: camelCase for functions (convention). EK9: camelCase for functions and methods, PascalCase for programs, services, and abstract function types.","keywords":["PascalCase","camelCase","convention","function","identifier","immutable","method","naming","operator","program","pure","service","side-effect","verb"],"primaryTopics":[],"typicalErrors":[{"error":"E50060","correct":"processor.applyDiscount(0.10)","incorrect":"processor.setDiscount(0.10)","explanation":"The method is named 'applyDiscount' not 'setDiscount'. Calling a method that does not exist on the type produces a resolution error. See ek9 -h E50060 for details."}],"companions":[]}
{"id":295,"category":"Variable Naming Rules and Conventions","question":"Can I use single-letter variable names in EK9?","url":"https://ek9.io/qa/QA0295.html","alternatePhrasings":["Are single-character variable names allowed in EK9?","Can I use i, j, k as loop counters in EK9?","Why does EK9 allow single-letter names but ban two-letter names like tmp?"],"answer":"Yes. Single-character variable names are always allowed in EK9. The compiler exempts them because mathematical conventions and loop counters are universally understood.\n\nALWAYS FINE\nMathematical variables: x, y, z for coordinates and equations. Loop counters: i, j, k for indices. Generic type parameters: T, K, V, U, S, E for parameterised types.\n\nACCEPTABLE\nShort lambdas and inline functions where the type makes the meaning clear from context. When iterating a typed collection, a single letter can be sufficient: for c in customers.\n\nBETTER TO AVOID\nWhen the meaning is not immediately clear from surrounding context. In a long function body, prefer descriptive names even for loop variables: for customerIndex in 0 ... length customers.\n\nWHY SINGLE-CHAR IS ALLOWED\nThe compiler trusts single-character names as intentional. A developer who writes 'x' knows exactly what they mean in context. The danger starts with two-plus character names that LOOK meaningful but carry no information: temp, data, obj, val. These create false confidence that the name communicates something.\n\nKEY INSIGHT\nSingle-letter names are honest about being short. Names like 'data' pretend to be descriptive but tell you nothing about what data they hold. EK9 bans the pretenders but trusts the honest ones.\n\nCOMPARISON\nPython and Java allow any name and rely on linters. Rust warns on non-snake-case but allows any identifier. EK9 enforces at compile time but trusts single-character names as intentional shorthand.\n\nSee Q290 for banned names. See Q292 for variable conventions. See Q80 for for-range with counters.","ek9Example":"defines module qa.naming.singleletter\n\n  defines function\n\n    calculateDistance() as pure\n      ->\n        x as Float\n        y as Float\n      <- d as Float: sqrt (x * x + y * y)\n\n    computeQuadratic() as pure\n      ->\n        a as Float\n        b as Float\n        c as Float\n        x as Float\n      <- y as Float: a * x * x + b * x + c\n\n  defines program\n\n    SingleLetterDemo()\n      stdout <- Stdout()\n\n      // === MATH VARIABLES: x, y, z ===\n\n      x <- 3.0\n      y <- 4.0\n      z <- calculateDistance(x, y)\n      stdout.println(`Distance from (${x}, ${y}): ${z}`)\n\n      // === QUADRATIC: a, b, c, x ===\n\n      a <- 1.0\n      b <- -3.0\n      c <- 2.0\n      result <- computeQuadratic(a: a, b: b, c: c, x: x)\n      stdout.println(`f(${x}) = ${result}`)\n\n      // === LOOP COUNTERS: i ===\n\n      total <- for i in 1 ... 5\n        <- rtn <- 0\n        rtn: rtn + i\n      stdout.println(`Sum 1-5: ${total}`)\n\n      // === NESTED LOOPS: i, j ===\n\n      grid <- for i in 1 ... 3\n        <- rtn <- \"\"\n        row <- for j in 1 ... 3\n          <- inner <- \"\"\n          if length inner > 0\n            inner: inner + \",\"\n          inner: inner + $(i * j)\n        if length rtn > 0\n          rtn: rtn + \" | \"\n        rtn: rtn + row\n      stdout.println(`Grid: ${grid}`)\n\n      // === COLLECTION ITERATION: descriptive preferred ===\n\n      names <- [\"Alice\", \"Bob\", \"Charlie\"]\n      for n in names\n        stdout.println(`Hello, ${n}`)\n\n      // === COMPARISON: short vs descriptive ===\n\n      customerNames <- [\"Alice\", \"Bob\", \"Charlie\"]\n      for customerName in customerNames\n        stdout.println(`Customer: ${customerName}`)","migrationContext":"Java: any identifier allowed, no special treatment for single-char names, relies on IDE inspections. Python: any identifier allowed, PEP 8 advises against single-letter except for counters. Rust: any identifier allowed, clippy has no single-char restriction. Go: single-letter names are idiomatic for short-lived variables. C/C++: any identifier allowed, single-letter names common in math code. Kotlin: any identifier allowed. JavaScript: any identifier allowed. EK9: single-character names explicitly exempted from naming rules, compiler trusts them as intentional shorthand while banning multi-character generic names.","keywords":["allowed","character","convention","counter","exempt","generic","identifier","letter","loop","math","migrate","naming","parameter","short","single","type","variable"],"primaryTopics":[],"typicalErrors":[{"error":"E50001","correct":"z <- calculateDistance(x, y)","incorrect":"zXYZ <- calculateDistance(x, y)","explanation":"Renaming the variable means later references to 'z' become unresolved, triggering E50001. Variable names must be consistent. See ek9 -h E50001 for details."}],"companions":[]}
{"id":296,"category":"Variable Naming Rules and Conventions","question":"Why does EK9 ban common names that every other language allows?","url":"https://ek9.io/qa/QA0296.html","alternatePhrasings":["What evidence supports banning variable names like temp and data?","Why is EK9 so strict about variable naming?","What disasters were caused by poor variable naming?"],"answer":"EK9 bans non-descriptive names because decades of research and catastrophic real-world failures demonstrate that naming quality is a safety mechanism, not a style preference.\n\nTHE RESEARCH EVIDENCE\nLawrie et al. (IEEE 2006) found non-descriptive names increase code comprehension time by 19-31%. Butler et al. (ICPC 2010) showed generic variable names correlate with 2.7x higher defect density. Hofmeister et al. (MSR 2017) found variables with generic names appear in 3.2x more bug reports. Code reviews consistently identify naming as the top maintainability issue.\n\nREAL-WORLD DISASTERS\nMars Climate Orbiter (1999, $125M lost): variables named with unclear units caused a metric/imperial conversion failure. Therac-25 (1985-87, 6 deaths): generic flag variables masked race conditions in radiation therapy software. Toyota Unintended Acceleration (2009-11, 89 deaths): NASA review found non-descriptive variables in throttle control code. Ariane 5 (1996, $370M lost): variable reuse with unclear naming contributed to a type conversion overflow.\n\nWHY OTHER LANGUAGES DO NOT DO THIS\nHistorical backward compatibility prevents Java, Python, and C++ from adding naming restrictions. The linter-based approach (Checkstyle, ESLint, pylint) treats naming as optional and configurable. The culture of minimal compiler enforcement assumes developers will self-police.\n\nWHY EK9 CAN DO THIS\nEK9 is a new language with no legacy codebases to break. The compiler is the right enforcement point because it cannot be misconfigured, skipped, or disabled. Every developer on every project gets the same naming quality.\n\nTHE PHILOSOPHY\nNaming is not a style choice. It is a safety mechanism. Self-documenting code prevents bugs. The cost is 2 seconds of thought per variable. The benefit is preventing the next $125M naming disaster.\n\nSee Q290 for the banned list. See Q297 for a renaming guide. See Q144 for similar evidence-based design philosophy.","ek9Example":"defines module qa.naming.evidence\n\n  defines function\n\n    convertTemperature() as pure\n      -> fahrenheitReading as Float\n      <- celsiusReading as Float: (fahrenheitReading - 32.0) * 5.0 / 9.0\n\n    calculateFuelEfficiency() as pure\n      ->\n        distanceTravelled as Float\n        fuelConsumed as Float\n      <- kilometresPerLitre as Float: Float()\n      if fuelConsumed > 0.0\n        kilometresPerLitre: distanceTravelled / fuelConsumed\n\n    isWithinTolerance() as pure\n      ->\n        measuredPressure as Float\n        targetPressure as Float\n        tolerancePercent as Float\n      <- withinRange as Boolean: false\n      allowedDeviation <- targetPressure * (tolerancePercent / 100.0)\n      lowerBound <- targetPressure - allowedDeviation\n      upperBound <- targetPressure + allowedDeviation\n      withinRange: measuredPressure >= lowerBound and measuredPressure <= upperBound\n\n  defines program\n\n    NamingEvidenceDemo()\n      stdout <- Stdout()\n\n      // === UNIT CLARITY: Prevents Mars Climate Orbiter-style failures ===\n\n      fahrenheitReading <- 212.0\n      celsiusReading <- convertTemperature(fahrenheitReading)\n      stdout.println(`${fahrenheitReading}F = ${celsiusReading}C`)\n\n      // === DESCRIPTIVE CALCULATIONS: No ambiguity about what is measured ===\n\n      distanceTravelled <- 450.0\n      fuelConsumed <- 35.0\n      kilometresPerLitre <- calculateFuelEfficiency(distanceTravelled, fuelConsumed)\n      stdout.println(`Efficiency: ${kilometresPerLitre} km/L`)\n\n      // === MEANINGFUL FLAGS: No generic flag variables ===\n\n      measuredPressure <- 14.5\n      targetPressure <- 14.7\n      tolerancePercent <- 5.0\n      pressureOk <- isWithinTolerance(measuredPressure, targetPressure, tolerancePercent)\n      stdout.println(`Pressure within tolerance: ${pressureOk}`)\n\n      // === EVERY NAME TELLS ITS STORY ===\n\n      sensorReadings <- [14.2, 14.5, 14.8, 15.1, 14.6]\n      withinCount <- 0\n      for reading in sensorReadings\n        if isWithinTolerance(reading, targetPressure, tolerancePercent)\n          withinCount++\n      stdout.println(`${withinCount} of ${length sensorReadings} readings within tolerance`)","migrationContext":"Java: relies on Checkstyle, SonarQube, and code reviews for naming quality, all optional and configurable. Python: PEP 8 is advisory, linters like pylint flag poor names as warnings. Rust: clippy provides some naming guidance but nothing about descriptive quality. Go: golint is advisory, no enforcement of descriptive naming. C/C++: no naming enforcement, relies entirely on code reviews. Kotlin: detekt provides optional naming rules. JavaScript: ESLint can flag naming issues but rules are optional. EK9: compiler enforces descriptive naming at compile time based on research evidence, cannot be disabled or configured away.","keywords":["Mars","Therac","Toyota","ban","bug","convention","defect","disaster","evidence","identifier","migrate","naming","opinionated","philosophy","research","safety","why"],"primaryTopics":[],"typicalErrors":[{"error":"E11031","correct":"fahrenheitReading","incorrect":"temp","explanation":"The name 'temp' is non-descriptive and banned. Using 'fahrenheitReading' prevents Mars Climate Orbiter-style unit confusion. See ek9 -h E11031 for details."},{"error":"E11031","correct":"pressureOk","incorrect":"flag","explanation":"The name 'flag' is non-descriptive and banned. Use a name like 'pressureOk' that describes what the boolean represents. See ek9 -h E11031 for details."}],"companions":[]}
{"id":297,"category":"Variable Naming Rules and Conventions","question":"What should I name my variable instead of temp or data?","url":"https://ek9.io/qa/QA0297.html","alternatePhrasings":["How do I rename temp, data, flag, and value in EK9?","What are good alternatives to banned variable names in EK9?","What descriptive name should I use instead of obj or buf?"],"answer":"Ask yourself: what does this variable REPRESENT? The answer to that question IS the name. Here are specific alternatives for every banned name.\n\nRENAMING TIER 1 NAMES (E11031)\n  temp/tmp: swapHolder, intermediateResult, pendingEntry, stagingArea\n  flag/flg: isComplete, hasPermission, shouldRetry, needsUpdate\n  data/dat: customerRecord, sensorReading, responsePayload, configSettings\n  object/obj: currentItem, targetEntity, parsedElement, selectedWidget\n  value/val: totalPrice, measuredWeight, userInput, computedScore\n  buffer/buf: outputBuilder, messageAccumulator, readChunk, uploadContent\n\nRENAMING OPERATOR KEYWORDS (E11032)\n  empty: isBlank, emptyBasket, noResults, clearedQueue\n  length: nameLength, pathSize, messageCount, arrayExtent\n  contains: hasItem, includesKey, foundMatch, holdingEntry\n  abs: absoluteDistance, magnitude, positiveAmount\n  sqrt: squareRoot, rootApproximation\n  close: closeFile, shutdownConnection, terminateSession\n  matches: foundItems, matchingRecords, searchHits, filteredResults\n\nTHE TECHNIQUE\nWhen you reach for a generic name, pause and ask three questions: 1. What kind of thing is this? (a price, a name, a sensor reading) 2. Where did it come from? (user input, database query, calculation) 3. What will it be used for? (display, comparison, accumulation). Any of these answers produces a better name than temp or data.\n\nSee Q290 for the complete banned list. See Q296 for why these names are banned. See Q292 for naming conventions.","ek9Example":"defines module qa.naming.renameguide\n\n  defines function\n\n    performSwap() as pure\n      ->\n        firstItem as String\n        secondItem as String\n      <-\n        swappedPair as String: `${secondItem}, ${firstItem}`\n\n    isWidgetItem()\n      -> item as String\n      <- isMatch as Boolean?\n\n      widgetLabel <- \"Widget\"\n      isMatch: item contains widgetLabel\n\n    retryConnection()\n      -> maxAttempts as Integer\n      <- connectionAttempts as Integer: 0\n\n      isRetryNeeded <- Boolean(true)\n      while isRetryNeeded and connectionAttempts < maxAttempts\n        connectionAttempts++\n        if connectionAttempts >= maxAttempts\n          isRetryNeeded: false\n\n  defines program\n\n    RenamingGuideDemo()\n      stdout <- Stdout()\n\n      // === SCENARIO 1: SWAP (temp -> swapHolder) ===\n\n      firstPrice <- 100.0\n      secondPrice <- 200.0\n      swapHolder <- firstPrice\n      firstPrice := secondPrice\n      secondPrice := swapHolder\n      stdout.println(`Swapped: ${firstPrice}, ${secondPrice}`)\n\n      // === SCENARIO 2: CONDITION (flag -> isRetryNeeded) ===\n\n      connectionAttempts <- retryConnection(3)\n      stdout.println(`Connected after ${connectionAttempts} attempts`)\n\n      // === SCENARIO 3: PROCESSING (data -> customerOrders) ===\n\n      customerOrders <- [\"Laptop\", \"Mouse\", \"Keyboard\"]\n      orderSummary <- \"\"\n      for orderItem in customerOrders\n        if length orderSummary > 0\n          orderSummary: orderSummary + \", \"\n        orderSummary: orderSummary + orderItem\n      stdout.println(`Orders: ${orderSummary}`)\n\n      // === SCENARIO 4: COLLECTION STATE (empty -> emptyBasket) ===\n\n      shoppingBasket <- List() of String\n      emptyBasket <- length shoppingBasket == 0\n      stdout.println(`Basket empty: ${emptyBasket}`)\n\n      widgetName <- \"Widget\"\n      shoppingBasket += widgetName\n      hasItems <- length shoppingBasket > 0\n      stdout.println(`Has items: ${hasItems}`)\n\n      // === SCENARIO 5: STRING MEASUREMENT (length -> nameLength) ===\n\n      customerName <- \"Alice Wonderland\"\n      nameLength <- length customerName\n      longNameThreshold <- 10\n      isLongName <- nameLength > longNameThreshold\n      stdout.println(`Name: ${customerName}, Length: ${nameLength}, Long: ${isLongName}`)\n\n      // === SCENARIO 6: SEARCH RESULTS (matches -> searchHits) ===\n\n      inventory <- [\"Widget\", \"Gadget\", \"Widget Pro\", \"Sprocket\"]\n      searchTerm <- widgetName\n      searchHits <- List() of String\n      for inventoryItem in inventory\n        if inventoryItem contains searchTerm\n          searchHits += inventoryItem\n      stdout.println(`Found ${length searchHits} items matching '${searchTerm}'`)","migrationContext":"Java: rename via IDE refactoring (IntelliJ Shift+F6), no compile-time naming enforcement. Python: rename manually or with rope refactoring, no naming enforcement. Rust: rename via rust-analyzer, no naming enforcement for descriptive quality. Go: rename via gopls, no naming enforcement. C/C++: rename via clangd or manual find-replace, no naming enforcement. Kotlin: rename via IntelliJ, no naming enforcement. JavaScript: rename via IDE, ESLint naming rules are optional. EK9: compiler forces you to think about naming at the point of declaration, alternatives shown in error messages.","keywords":["alternative","buffer","convention","data","descriptive","flag","guide","identifier","instead","migrate","naming","object","practical","rename","replace","temp","value"],"primaryTopics":[],"typicalErrors":[{"error":"E11031","correct":"swapHolder","incorrect":"temp","explanation":"The name 'temp' is non-descriptive and banned. Use 'swapHolder' to describe the variable's role in the swap operation. See ek9 -h E11031 for details."},{"error":"E11032","correct":"emptyBasket","incorrect":"empty","explanation":"The name 'empty' shadows a built-in operator keyword and is rejected. Use a descriptive alternative like 'emptyBasket'. See ek9 -h E11032 for details."},{"error":"E11032","correct":"searchHits","incorrect":"matches","explanation":"The name 'matches' shadows a built-in operator keyword and is rejected. Use a descriptive alternative like 'searchHits'. See ek9 -h E11032 for details."}],"companions":[]}
{"id":298,"category":"Sealed Types and Traits","question":"What is allow only on traits in EK9?","url":"https://ek9.io/qa/QA0298.html","alternatePhrasings":["How do I create a sealed trait in EK9?","How does EK9 restrict which classes can implement a trait?","What is the EK9 equivalent of Java sealed interfaces?"],"answer":"The 'allow only' clause on a trait creates a sealed trait, restricting which classes may implement it. Only the classes explicitly listed can use 'with trait of' to implement the trait.\n\nSEALED TRAIT SYNTAX\nDeclare the permitted classes after 'allow only':\n  Shape allow only Circle, Square, Triangle\n    area() as abstract\n      <- rtn as Float?\n\nTRANSITIVE ENFORCEMENT\nThe restriction is checked transitively. If a class acquires the sealed trait through an intermediate trait or abstract class chain, it must still be in the 'allow only' list.\n\nABSTRACT EXEMPTION\nAbstract classes are exempt from the check because they cannot be instantiated. The sealed set controls concrete runtime types only.\n\nDYNAMIC CLASSES\nDynamic classes (anonymous classes created inline) are always rejected because their generated names cannot appear in an 'allow only' list.\n\nSee Q299 for how 'allow only' works with dispatchers. See Q300 for why abstract classes cannot be in the list. See Q607 for why sealed classes need 'as open'. See Q608 for sealed classes vs sealed traits. See Q609 for cross-module sealed enforcement.","ek9Example":"defines module qa.sealedtraits.allowonly\n\n  defines trait\n\n    Shape allow only Circle, Square, Triangle\n      area() as abstract\n        <- rtn as Float?\n\n  defines class\n\n    Circle with trait of Shape\n      override area()\n        <- rtn as Float: 3.14\n\n    Square with trait of Shape\n      override area()\n        <- rtn as Float: 1.0\n\n    Triangle with trait of Shape\n      override area()\n        <- rtn as Float: 0.5\n\n  defines function\n\n    testSealedTrait()\n      circle <- Circle()\n      square <- Square()\n      triangle <- Triangle()\n      require circle?\n      require square?\n      require triangle?","migrationContext":"Java: sealed interfaces with 'permits' clause (Java 17+). Kotlin: sealed interfaces restrict implementations to same package/module. Rust: no sealed traits, but orphan rule prevents external trait implementations. Swift: no direct equivalent, uses access control. EK9: 'allow only' provides explicit listing of permitted concrete implementations.","keywords":["abstract","allow","allow-only","anonymous","capture","class","closed","closure","dynamic","exhaustive","migrate","only","permit","restrict","sealed","trait","transitive"],"primaryTopics":["allow only","sealed trait","sealed type"],"typicalErrors":[{"error":"E50001","correct":"circle <- Circle()","incorrect":"circleXYZ <- Circle()","explanation":"Renaming the variable means later references to 'circle' become unresolved, triggering E50001. Variable names must be consistent. See ek9 -h E50001 for details."},{"error":"E05120","correct":"Circle with trait of Shape\n      override area()\n        <- rtn as Float: 3.14","incorrect":"Circle with trait of Shape\n      area()\n        <- rtn as Float: 3.14","explanation":"When implementing a trait's abstract method, 'override' is required. Omitting it triggers E05120 because the method is inherited from the trait. See ek9 -h E05120 for details."},{"error":"E05240","correct":"Shape allow only Circle, Square, Triangle","incorrect":"Shape allow only Circle, Square","explanation":"Only classes explicitly listed in the 'allow only' clause may implement the sealed trait. Adding Hexagon without listing it in 'Shape allow only Circle, Square, Triangle' triggers E02030. See ek9 -h E02030 for details."}],"companions":[]}
{"id":299,"category":"Sealed Types and Traits","question":"How does allow only work with dispatchers in EK9?","url":"https://ek9.io/qa/QA0299.html","alternatePhrasings":["Does EK9 check exhaustive dispatch on sealed traits?","How do I get exhaustive matching in EK9?","What is the EK9 equivalent of Java sealed switch?"],"answer":"When a dispatcher's parameter type is a sealed trait (one with 'allow only'), the compiler requires handlers for all permitted types. This provides exhaustive matching, similar to Java's sealed switch or Kotlin's sealed when.\n\nEXHAUSTIVE DISPATCH\nA dispatcher on a sealed trait must have a handler for every type in the 'allow only' list:\n  ShapeProcessor\n    process() as dispatcher\n      -> shape as Shape\n    process()\n      -> circle as Circle\n    process()\n      -> square as Square\n    process()\n      -> triangle as Triangle\n\nCOMPILE-TIME SAFETY\nWhen you add a new type to the 'allow only' list, every dispatcher that operates on that trait will fail to compile until a handler is added. This guarantees all dispatch points are updated.\n\nUNSEALED TRAITS\nDispatchers on unsealed traits (without 'allow only') do not require exhaustive handlers. The entry point acts as a catch-all fallback.\n\nSee Q298 for 'allow only' basics. See Q105 for dispatcher patterns. See Q618 for sealed exhaustive dispatch deep dive.","ek9Example":"defines module qa.sealedtraits.dispatchers\n\n  defines trait\n\n    Shape allow only Circle, Square, Triangle\n      area() as abstract\n        <- rtn as Float?\n\n  defines class\n\n    Circle with trait of Shape\n      override area()\n        <- rtn as Float: 3.14\n\n    Square with trait of Shape\n      override area()\n        <- rtn as Float: 1.0\n\n    Triangle with trait of Shape\n      override area()\n        <- rtn as Float: 0.5\n\n    ShapeProcessor\n\n      process() as dispatcher\n        -> shape as Shape\n        content <- \"Unknown shape\"\n        require content?\n\n      process()\n        -> circle as Circle\n        content <- \"Circle\"\n        require content?\n\n      process()\n        -> square as Square\n        content <- \"Square\"\n        require content?\n\n      process()\n        -> triangle as Triangle\n        content <- \"Triangle\"\n        require content?\n\n  defines function\n\n    testDispatchers()\n      processor <- ShapeProcessor()\n      require processor?","migrationContext":"Java: sealed interfaces + switch expressions with exhaustive pattern matching (Java 21+). Kotlin: sealed classes/interfaces + when expression with exhaustive checking. Rust: enum variants + match expression with exhaustive checking. Scala: sealed traits + match expression. EK9: dispatchers on sealed traits with compile-time exhaustiveness checking.","keywords":["allow","allow-only","closed","complete","dispatch","dispatcher","exhaustive","handler","match","migrate","missing","only","permit","sealed","switch"],"primaryTopics":[],"typicalErrors":[{"error":"E05120","correct":"Circle with trait of Shape\n      override area()\n        <- rtn as Float: 3.14","incorrect":"Circle with trait of Shape\n      area()\n        <- rtn as Float: 3.14","explanation":"Implementing a trait's abstract method requires the 'override' keyword. Omitting it causes E05120 because the compiler sees an inherited method being redefined without acknowledgement. See ek9 -h E05120 for details."},{"error":"E50001","correct":"processor <- ShapeProcessor()","incorrect":"processorXYZ <- ShapeProcessor()","explanation":"Renaming the variable means later references to 'processor' become unresolved, triggering E50001. Variable names must be consistent. See ek9 -h E50001 for details."},{"error":"E05240","correct":"Shape allow only Circle, Square, Triangle","incorrect":"Shape allow only Circle, Square","explanation":"A class not in the 'allow only' list cannot implement the sealed trait. Hexagon is not listed in 'Shape allow only Circle, Square, Triangle' so it triggers E02030. See ek9 -h E02030 for details."}],"companions":[]}
{"id":300,"category":"Sealed Types and Traits","question":"Can abstract classes be in an allow only list in EK9?","url":"https://ek9.io/qa/QA0300.html","alternatePhrasings":["Why can't abstract classes be in allow only?","What happens if I put an abstract class in allow only?","Does allow only work with abstract classes?"],"answer":"No, abstract classes cannot appear in a trait's 'allow only' list. The list defines the exhaustive set of concrete runtime types that may implement the trait. Since abstract classes cannot be instantiated, listing them is meaningless.\n\nABSTRACT CLASSES ARE EXEMPT\nAbstract classes can still implement sealed traits freely. They are exempt from the 'allow only' enforcement check because they cannot create runtime instances:\n  AbstractHelper with trait of SealedTrait as abstract  //OK\n\nBUT NOT IN THE LIST\nThe 'allow only' list should contain only concrete classes:\n  SealedTrait allow only ConcreteA, ConcreteB  //CORRECT\n  SealedTrait allow only AbstractBase, ConcreteA  //ERROR: E05250\n\nTRANSITIVE CHECKING\nConcrete subclasses of an abstract helper class that implements a sealed trait will be checked transitively. They must be in the 'allow only' list or get E05240.\n\nSee Q298 for 'allow only' basics. See Q299 for dispatcher exhaustiveness. See Q607 for why sealed classes must be open.","ek9Example":"defines module qa.sealedtraits.abstract.list\n\n  defines trait\n\n    Workable allow only ConcreteAlpha, ConcreteBeta\n      doWork() as abstract\n\n  defines class\n\n    //AbstractHelper implements the sealed trait - allowed (abstract exempt)\n    AbstractHelper with trait of Workable as abstract\n      override doWork()\n        content <- \"Abstract\"\n        require content?\n\n    //ConcreteAlpha is in the allow only list - allowed\n    ConcreteAlpha with trait of Workable\n      override doWork()\n        content <- \"Alpha\"\n        require content?\n\n    //ConcreteBeta is in the allow only list - allowed\n    ConcreteBeta is AbstractHelper\n      override doWork()\n        content <- \"Beta\"\n        require content?\n\n  defines function\n\n    testAbstractExemption()\n      alpha <- ConcreteAlpha()\n      beta <- ConcreteBeta()\n      require alpha?\n      require beta?","migrationContext":"Java: sealed interfaces allow abstract classes in permits clause (different design choice). Kotlin: sealed hierarchy allows abstract intermediaries. Rust: no sealed traits, enums are always concrete. EK9: 'allow only' list is strictly for concrete types, abstract classes can implement freely but cannot be listed.","keywords":["abstract","allow","allow-only","class","closed","concrete","exempt","exhaustive","instantiate","list","only","permit","runtime","sealed","trait"],"primaryTopics":[],"typicalErrors":[{"error":"E05120","correct":"ConcreteAlpha with trait of Workable\n      override doWork()","incorrect":"ConcreteAlpha with trait of Workable\n      doWork()","explanation":"Implementing an abstract method from a trait requires 'override'. Without it the compiler reports E05120 because the method signature is inherited. See ek9 -h E05120 for details."},{"error":"E50010","correct":"ConcreteBeta is AbstractHelper","incorrect":"UnlistedClass is AbstractHelper","explanation":"A concrete class extending AbstractHelper inherits the sealed trait Workable. If that concrete class is not in the 'allow only' list, the compiler rejects it because the sealed trait restricts which concrete types may implement it. See ek9 -h E50010 for details."},{"error":"E05240","correct":"Workable allow only ConcreteAlpha, ConcreteBeta","incorrect":"Workable allow only ConcreteBeta","explanation":"ConcreteGamma is not in the 'allow only' list for the sealed trait Workable. Only ConcreteAlpha and ConcreteBeta are permitted to implement it. Adding an unlisted concrete class triggers E02030. See ek9 -h E02030 for details."}],"companions":[]}
{"id":301,"category":"Sealed Types and Traits","question":"How do I restrict which classes can extend my class in EK9?","url":"https://ek9.io/qa/QA0301.html","alternatePhrasings":["What is allow only on classes in EK9?","How do sealed classes work in EK9?","Can I use allow only on a class instead of a trait?"],"answer":"EK9 supports 'allow only' on both traits AND classes. A sealed class must be declared 'as open' (since EK9 classes are closed by default) and lists the permitted subclasses.\n\nSEALED CLASS SYNTAX\nDeclare the class as open with the permitted subclasses:\n  Shape as open allow only Circle, Square, Triangle\n    area()\n      <- rtn as Float: 0.0\n\nKEY DIFFERENCE FROM TRAITS\nClasses are closed by default, so 'as open' is required. Traits are always open, so they don't need it.\n\nCHAINED SEALING\nA permitted subclass can itself be sealed:\n  Vehicle as open allow only Car, Truck\n  Car extends Vehicle as open allow only Sedan, Hatchback\n\nThis is identical to Java 17's sealed classes with 'permits' and Kotlin's sealed classes.\n\nSee Q298 for sealed traits, Q299 for dispatchers with sealed types.","ek9Example":"defines module qa.sealedclasses.allowonly\n\n  defines class\n\n    Shape allow only Circle, Square, Triangle as open\n      area()\n        <- rtn as Float: 0.0\n\n    Circle extends Shape\n      override area()\n        <- rtn as Float: 3.14\n\n    Square extends Shape\n      override area()\n        <- rtn as Float: 1.0\n\n    Triangle extends Shape\n      override area()\n        <- rtn as Float: 0.5\n\n  defines function\n\n    testSealedClass()\n      circle <- Circle()\n      square <- Square()\n      triangle <- Triangle()\n      require circle?\n      require square?\n      require triangle?","migrationContext":"Java: sealed classes with 'permits' clause (Java 17+). Kotlin: sealed classes restrict subclasses to same file. Rust: no sealed classes, but enum variants provide similar exhaustiveness. Swift: no direct equivalent. EK9: 'allow only' on classes provides explicit listing of permitted subclasses.","keywords":["allow","allow-only","class","closed","exhaustive","extend","migrate","only","open","permit","restrict","sealed","subclass"],"primaryTopics":[],"typicalErrors":[{"error":"E05030","correct":"Shape allow only Circle, Square, Triangle as open","incorrect":"Shape allow only Circle, Square, Triangle","explanation":"EK9 classes are closed by default. Without 'as open', no class can extend Shape. The compiler reports E05030 because the type is not open for extension. See ek9 -h E05030 for details."},{"error":"E05120","correct":"Circle extends Shape\n      override area()\n        <- rtn as Float: 3.14","incorrect":"Circle extends Shape\n      area()\n        <- rtn as Float: 3.14","explanation":"Overriding a parent class method requires the 'override' keyword. Omitting it triggers E05120 because the method is inherited from Shape. See ek9 -h E05120 for details."},{"error":"E50010","correct":"Circle extends Shape","incorrect":"Pentagon extends Shape","explanation":"Pentagon is not in Shape's 'allow only' list. Attempting to extend a sealed class with an unlisted type is rejected by the compiler. See ek9 -h E50010 for details."},{"error":"E50010","correct":"Shape allow only Circle, Square, Triangle as open","incorrect":"Pentagon extends Shape\n      override area()\n        <- rtn as Float: 1.72","explanation":"Pentagon is not listed in 'Shape allow only Circle, Square, Triangle'. Extending a sealed class with a class not in the 'allow only' list triggers E50010 at PRE_IR_CHECKS. See ek9 -h E50010 for details."}],"companions":[]}
{"id":302,"category":"Sealed Types and Traits","question":"How do dispatchers work with sealed classes in EK9?","url":"https://ek9.io/qa/QA0302.html","alternatePhrasings":["Can I use dispatchers with sealed classes?","Does dispatcher exhaustiveness work for sealed classes?","How do I dispatch on a sealed class?"],"answer":"Dispatchers work identically with sealed classes and sealed traits. When a dispatcher's parameter type is a sealed class (one with 'allow only'), the compiler requires handlers for ALL permitted subclasses.\n\nDISPATCHER ON SEALED CLASS\n  ShapeProcessor\n    process() as dispatcher\n      -> shape as Shape\n      <- rtn as String: \"Unknown\"\n    process()\n      -> circle as Circle\n      <- rtn as String: \"Circle\"\n    process()\n      -> square as Square\n      <- rtn as String: \"Square\"\n\nIf any permitted type is missing a handler, the compiler reports E05260.\n\nThis provides compile-time exhaustive pattern matching, ensuring that when a new subclass is added to the 'allow only' list, all dispatchers are updated.\n\nSee Q299 for dispatchers with sealed traits, Q301 for sealed class basics. See Q618 for sealed exhaustive dispatch deep dive.","ek9Example":"defines module qa.sealedclasses.dispatcher\n\n  defines class\n\n    Shape allow only Circle, Square as open\n      name()\n        <- rtn as String: \"Shape\"\n\n    Circle extends Shape\n      override name()\n        <- rtn as String: \"Circle\"\n\n    Square extends Shape\n      override name()\n        <- rtn as String: \"Square\"\n\n    ShapeProcessor\n\n      process() as dispatcher\n        -> shape as Shape\n        <- rtn as String: \"Unknown\"\n\n      process()\n        -> circle as Circle\n        <- rtn as String: \"Circle\"\n\n      process()\n        -> square as Square\n        <- rtn as String: \"Square\"\n\n  defines function\n\n    testDispatcher()\n      processor <- ShapeProcessor()\n      circleResult <- processor.process(Circle())\n      squareResult <- processor.process(Square())\n      require circleResult?\n      require squareResult?","migrationContext":"Java: exhaustive switch on sealed classes (Java 21+). Kotlin: exhaustive when expressions on sealed classes. Rust: exhaustive match on enums. EK9: dispatcher exhaustiveness on sealed classes provides the same compile-time guarantee.","keywords":["allow","allow-only","class","closed","dispatch","dispatcher","exhaustive","handler","matching","only","pattern","permit","sealed"],"primaryTopics":[],"typicalErrors":[{"error":"E05030","correct":"Shape allow only Circle, Square as open","incorrect":"Shape allow only Circle, Square","explanation":"Classes are closed by default in EK9. Without 'as open', Circle and Square cannot extend Shape. The compiler reports E05030 because the type is not open for extension. See ek9 -h E05030 for details."},{"error":"E05120","correct":"Circle extends Shape\n      override name()\n        <- rtn as String: \"Circle\"","incorrect":"Circle extends Shape\n      name()\n        <- rtn as String: \"Circle\"","explanation":"Overriding a method inherited from Shape requires the 'override' keyword. Without it the compiler reports E05120. See ek9 -h E05120 for details."},{"error":"E50010","correct":"Shape allow only Circle, Square as open","incorrect":"Triangle extends Shape\n      override name()\n        <- rtn as String: \"Triangle\"","explanation":"Triangle is not in 'Shape allow only Circle, Square'. A class not in the sealed class's 'allow only' list cannot extend it. Adding Triangle without updating the list triggers E50010. See ek9 -h E50010 for details."}],"companions":[],"oracleToolHint":{"tool":"ek9_scaffold","intent":"dispatcher","description":"Oracle can generate a dispatcher with sealed class type matching."}}
{"id":303,"category":"Sealed Types and Traits","question":"Can a permitted subclass also be sealed in EK9?","url":"https://ek9.io/qa/QA0303.html","alternatePhrasings":["How does chained sealing work in EK9?","Can I have nested allow only in EK9?","How do I create a multi-level sealed hierarchy?"],"answer":"Yes! A permitted subclass can itself be sealed, creating a chained hierarchy. Enforcement is transitive: all descendants must appear in every ancestor's allow only list.\n\nCHAINED SEALING EXAMPLE\n  Vehicle allow only Car, Truck, Sedan, Hatchback as open\n  Car extends Vehicle allow only Sedan, Hatchback as open\n  Sedan extends Car\n  Hatchback extends Car\n  Truck extends Vehicle\n\nTRANSITIVE ENFORCEMENT\nSedan and Hatchback extend Car, but Car extends Vehicle. Because Vehicle is sealed, Sedan and Hatchback must also appear in Vehicle's allow only list. This is transitive: every sealed ancestor must explicitly permit the descendant.\n\nENFORCEMENT AT EACH LEVEL\n- Vehicle permits: Car, Truck, Sedan, Hatchback\n- Car permits: Sedan, Hatchback only\n- Truck is not sealed (any class could extend it if it were 'as open')\n\nThis works with both classes and traits. Each sealed level provides its own exhaustive set.\n\nSee Q301 for sealed class basics, Q298 for sealed traits.","ek9Example":"defines module qa.sealedclasses.chained\n\n  defines class\n\n    Vehicle allow only Car, Truck, Sedan, Hatchback as open\n      describe()\n        <- rtn as String: \"Vehicle\"\n\n    Car extends Vehicle allow only Sedan, Hatchback as open\n      override describe()\n        <- rtn as String: \"Car\"\n\n    Sedan extends Car\n      override describe()\n        <- rtn as String: \"Sedan\"\n\n    Hatchback extends Car\n      override describe()\n        <- rtn as String: \"Hatchback\"\n\n    Truck extends Vehicle\n      override describe()\n        <- rtn as String: \"Truck\"\n\n  defines function\n\n    testChainedSealing()\n      sedan <- Sedan()\n      hatchback <- Hatchback()\n      truck <- Truck()\n      require sedan?\n      require hatchback?\n      require truck?","migrationContext":"Java: sealed class hierarchies with permits at each level (Java 17+). Kotlin: sealed classes in same file, nested sealed hierarchies supported. EK9: chained allow only provides the same hierarchical sealing.","keywords":["allow","allow-only","chained","class","closed","exhaustive","extend","hierarchy","level","nested","only","permit","sealed"],"primaryTopics":[],"typicalErrors":[{"error":"E05270","correct":"Vehicle allow only Car, Truck, Sedan, Hatchback as open","incorrect":"Vehicle allow only Car, Truck, Sedan, Hatchback","explanation":"EK9 classes are closed by default. Without 'as open', no subclass can extend Vehicle. The compiler reports E05030 because the type is not open for extension. See ek9 -h E05030 for details."},{"error":"E05240","correct":"Vehicle allow only Car, Truck, Sedan, Hatchback as open","incorrect":"Vehicle allow only Car, Truck as open","explanation":"Sedan and Hatchback extend Car which extends Vehicle. Because Vehicle is sealed, all concrete descendants including Sedan and Hatchback must appear in Vehicle's 'allow only' list. Omitting them triggers E05240. See ek9 -h E05240 for details."},{"error":"E05120","correct":"Car extends Vehicle allow only Sedan, Hatchback as open\n      override describe()\n        <- rtn as String: \"Car\"","incorrect":"Car extends Vehicle allow only Sedan, Hatchback as open\n      describe()\n        <- rtn as String: \"Car\"","explanation":"Overriding the describe() method inherited from Vehicle requires the 'override' keyword. Omitting it triggers E05120. See ek9 -h E05120 for details."},{"error":"E50010","correct":"Vehicle allow only Car, Truck, Sedan, Hatchback as open","incorrect":"SUV extends Vehicle\n      override describe()\n        <- rtn as String: \"SUV\"","explanation":"SUV is not in 'Vehicle allow only Car, Truck, Sedan, Hatchback'. A concrete class must be listed in every sealed ancestor's 'allow only' list. Adding SUV without updating Vehicle's list triggers E50010. See ek9 -h E50010 for details."}],"companions":[]}
{"id":304,"category":"Error Handling and Exceptions","question":"What is the require statement and when should I use it?","url":"https://ek9.io/qa/QA0304.html","alternatePhrasings":["How do I enforce preconditions in EK9?","What is the EK9 equivalent of assert or panic?","How do I validate method arguments in EK9?"],"answer":"The require statement validates preconditions in production code. When a require condition is false or unset, it throws an uncatchable exception that terminates execution immediately. This is similar to panic in Go or Rust.\n\nKEY CHARACTERISTICS\n- Uncatchable: require failures cannot be caught with try/catch. There is no recovery.\n- Always active: unlike Java assert which can be disabled, EK9 require is always checked.\n- For programming errors: use require when a failure means the caller has a bug.\n- Fail-fast: detects contract violations immediately rather than allowing corrupt state.\n\nWHEN TO USE REQUIRE\nUse require for:\n- Null/unset parameters when the method cannot accept unset values.\n- Collection must not be empty (require not items.empty()).\n- Method called in wrong object state (require status == OrderStatus.Created).\n- Internal invariants that should never be violated.\n\nWHEN NOT TO USE REQUIRE\nDo not use require for:\n- User input validation (use catchable exceptions or Result instead).\n- File not found or network timeout (these are recoverable).\n- Any condition that could reasonably fail due to external factors.\n\nREQUIRE VS THROW\nrequire: For programming errors and contract violations. Uncatchable. Similar to panic.\nthrow: For exceptional but potentially recoverable situations. Catchable with try/catch.\n\nSee Q134 for try/catch. See Q139 for error handling strategy. See Q305 for how require differs from assert. See Q136 for throwing exceptions.","ek9Example":"defines module qa.errorhandling.preconditions\n\n  defines type\n\n    OrderStatus\n      Created\n      Submitted\n      Shipped\n\n  defines class\n\n    <?-\n      Demonstrates require for precondition checking.\n      Require validates that callers honour the contract.\n    -?>\n    Order\n      items as List of String: List() of String\n      status as OrderStatus: OrderStatus.Created\n\n      <?-\n        Constructor with preconditions.\n        The caller must provide valid, non-empty data.\n      -?>\n      Order()\n        ->\n          customerId as String\n          initialItems as List of String\n        require customerId?\n        require initialItems?\n        require not initialItems.empty()\n        this.items :=: initialItems\n\n      <?-\n        Can only add items while order is in Created state.\n        Calling this after submit is a programming error.\n      -?>\n      addItem()\n        -> item as String\n        require item?\n        require status == OrderStatus.Created\n        items += item\n\n      <?-\n        Submit the order. Must have items and be in Created state.\n      -?>\n      submit()\n        require not items.empty()\n        require status == OrderStatus.Created\n        status: OrderStatus.Submitted\n\n      <?-\n        Ship the order. Must be in Submitted state.\n      -?>\n      ship()\n        require status == OrderStatus.Submitted\n        status: OrderStatus.Shipped\n\n      itemCount() as pure\n        <- rtn as Integer: length items\n\n      override operator ? as pure\n        <- rtn as Boolean: items? and status?\n\n      operator $ as pure\n        <- rtn as String: `Order[${status}, ${itemCount()} items]`\n\n  defines class\n\n    <?-\n      Demonstrates require for validating arguments\n      in a utility class.\n    -?>\n    PriceCalculator\n\n      <?-\n        Calculate total price. Both arguments must be set and valid.\n      -?>\n      calculateTotal()\n        ->\n          unitPrice as Float\n          quantity as Integer\n        <-\n          rtn as Float: Float()\n\n        require unitPrice?\n        require quantity?\n        require unitPrice >= 0.0\n        require quantity >= 0\n\n        rtn: unitPrice * Float(quantity)\n\n  defines program\n\n    RequirePreconditionsDemo()\n      stdout <- Stdout()\n\n      stdout.println(\"=== Order lifecycle with require ===\")\n\n      initialItems <- List() of String\n      initialItems += \"Widget\"\n      initialItems += \"Gadget\"\n\n      order <- Order(\"CUST-001\", initialItems)\n      stdout.println(\"Created: \" + $order)\n\n      order.addItem(\"Sprocket\")\n      stdout.println(\"Added item: \" + $order)\n\n      order.submit()\n      stdout.println(\"Submitted: \" + $order)\n\n      order.ship()\n      stdout.println(\"Shipped: \" + $order)\n\n      stdout.println(\"=== Price calculation with require ===\")\n\n      calc <- PriceCalculator()\n      total <- calc.calculateTotal(9.99, 3)\n      stdout.println(\"Total: \" + $total)","migrationContext":"Go: panic() is uncatchable unless recover() is used. Rust: panic!() unwinds the stack and terminates the thread. Java: assert can be disabled at runtime with -da flag (weak). C/C++: assert() can be compiled out with NDEBUG (weak). Python: assert can be disabled with -O flag. Kotlin: require() throws IllegalArgumentException (catchable, unlike EK9). EK9: require is always active, always uncatchable, stronger than all of these.","keywords":["argument","catch","contract","exception","guard","handle","invariant","isset","migrate","null-safe","panic","precondition","require","safe","uncatchable","validate"],"primaryTopics":["require","precondition","contract"],"typicalErrors":[{"error":"E50001","correct":"order <- Order(\"CUST-001\", initialItems)","incorrect":"orderXYZ <- Order(\"CUST-001\", initialItems)","explanation":"Renaming the variable means later references to 'order' become unresolved, triggering E50001. Variable names must be consistent. See ek9 -h E50001 for details."}],"companions":[]}
{"id":305,"category":"Error Handling and Exceptions","question":"What is the difference between require, assert, and throw?","url":"https://ek9.io/qa/QA0305.html","alternatePhrasings":["When should I use require vs assert vs throw?","How do I choose between require, assert, and throw?","What is the EK9 error handling hierarchy?"],"answer":"EK9 provides three distinct mechanisms for checking conditions, each for a different context.\n\nREQUIRE (Production Preconditions)\nContext: Any code (production, libraries, frameworks).\nBehavior: When the condition is false or unset, throws an uncatchable exception. Program terminates.\nPurpose: Enforce contracts and preconditions. Caller has a bug if this fails.\nCatchable: No. There is no recovery.\nAlways active: Yes. Cannot be disabled.\nExample: require quantity >= 0\n\nASSERT (Test Validation)\nContext: Only valid inside @Test programs. Using assert in production code causes compile error E81012.\nBehavior: When the condition is false, the test fails with structured output showing file, line, column, and the failed expression text.\nPurpose: Verify expected behavior in tests.\nCatchable: Not applicable (test infrastructure handles failures).\nAlways active: Yes, within test context.\nExample: assert result == 42\n\nTHROW (Recoverable Exceptions)\nContext: Any code.\nBehavior: Throws a catchable exception that can be handled with try/catch.\nPurpose: Signal exceptional but potentially recoverable conditions.\nCatchable: Yes, with try/catch.\nExample: throw Exception(\"File not found\")\n\nDECISION GUIDE\nIs this a test? Use assert.\nIs this a programming error or contract violation? Use require.\nCould this reasonably happen at runtime and be recovered from? Use throw.\nIs this expected failure in normal flow? Use Result type instead.\n\nKEY DISTINCTIONS\n- require is stronger than throw because it cannot be caught.\n- assert is compile-time restricted to tests, preventing production misuse.\n- throw and try/catch are for runtime error recovery.\n- Result is for expected failures that are not exceptional.\n\nSee Q134 for try/catch. See Q136 for throwing exceptions. See Q139 for try/catch vs Result. See Q304 for require in depth.","ek9Example":"defines module qa.errorhandling.require.vs.assert.vs.throwex\n\n  defines class\n\n    <?-\n      Simple domain class used in examples below.\n    -?>\n    Account\n      balance <- Float()\n\n      Account()\n        -> initialBalance as Float\n        require initialBalance?\n        require initialBalance >= 0.0\n        balance :=: initialBalance\n\n      deposit()\n        -> amount as Float\n        require amount?\n        require amount > 0.0\n        balance += amount\n\n      withdraw()\n        -> amount as Float\n        require amount?\n        require amount > 0.0\n        if amount > balance\n          throw Exception(\"Insufficient funds\")\n        balance -= amount\n\n      getBalance() as pure\n        <- rtn as Float: balance\n\n      override operator ? as pure\n        <- rtn as Boolean: balance?\n\n      operator $ as pure\n        <- rtn as String: `Account[balance=${balance}]`\n\n  defines program\n\n    RequireVsAssertVsThrowDemo()\n      stdout <- Stdout()\n\n      stdout.println(\"=== require: enforcing preconditions ===\")\n\n      account <- Account(100.0)\n      stdout.println(\"Created: \" + $account)\n\n      account.deposit(50.0)\n      stdout.println(\"After deposit: \" + $account)\n\n      stdout.println(\"=== throw: recoverable exception ===\")\n\n      try\n        account.withdraw(200.0)\n      catch\n        -> ex as Exception\n        stdout.println(\"Caught: \" + ex.reason())\n\n      stdout.println(\"Account still usable: \" + $account)\n\n      account.withdraw(30.0)\n      stdout.println(\"After valid withdraw: \" + $account)\n\n      stdout.println(\"=== Result: expected failures ===\")\n\n      stdout.println(\"Use Result for operations where failure is normal,\")\n      stdout.println(\"like parsing user input or searching a collection.\")","migrationContext":"Java: assert (can be disabled with -da), throw/try/catch, no require equivalent. Python: assert (disabled with -O), raise/try/except, no require. Go: panic (uncatchable without recover), no assert keyword, error return values. Rust: panic! (uncatchable), assert! (active in debug, removed in release with --release), Result for recoverable errors. Kotlin: require() (catchable IllegalArgumentException), assert (JVM assert), throw/try/catch. C/C++: assert() (disabled with NDEBUG), throw/try/catch. EK9: require is always active and uncatchable (stronger than all), assert is compile-time restricted to tests (cannot misuse in production), throw/try/catch for recoverable situations.","keywords":["E81012","assert","catch","comparison","decision","exception","handle","precondition","require","test","throw","uncatchable"],"primaryTopics":[],"typicalErrors":[{"error":"E07530","correct":"require amount > 0.0","incorrect":"require amount","explanation":"The require statement needs a Boolean expression. Passing a Float directly triggers E07530 — only compatible with Boolean type. Use a comparison like 'require amount > 0.0' or 'require amount?' to check if set. See ek9 -h E07530 for details."},{"error":"E04030","correct":"throw Exception(\"Insufficient funds\")","incorrect":"throw Account(100.0)","explanation":"The throw statement requires a value that extends Exception. Throwing a regular class like Account triggers E04030 — type must be of Exception type. Create a custom exception with 'extends Exception' for domain-specific errors. See ek9 -h E04030 for details."}],"companions":[]}
{"id":306,"category":"Error Handling and Exceptions","question":"How do I test that code throws an exception in EK9?","url":"https://ek9.io/qa/QA0306.html","alternatePhrasings":["How does assertThrows work in EK9?","How do I verify an exception is thrown in a test?","What is the EK9 equivalent of JUnit assertThrows?"],"answer":"EK9 provides assertThrows as a built-in keyword for verifying that an expression throws an expected exception type. It is only valid inside @Test programs.\n\nBASIC SYNTAX\nassertThrows(ExceptionType, expression)\nThe test passes if the expression throws an exception of the specified type. The test fails if no exception is thrown or a different type is thrown.\n\nAS A STATEMENT\nUse assertThrows when you only need to verify the exception occurs:\n  assertThrows(Exception, riskyFunction())\nIf riskyFunction() does not throw, the test fails with a structured error message showing the location, expression, expected type, and what actually happened.\n\nAS AN EXPRESSION (Capture)\nCapture the thrown exception for further inspection:\n  caught <- assertThrows(Exception, riskyFunction())\n  assert caught.reason()?\n  assert $caught == \"Expected error message\"\nThe returned value is the caught exception, allowing inspection of its reason, exit code, and custom fields.\n\nFAILURE OUTPUT\nWhen assertThrows fails, EK9 provides structured diagnostic output:\n  assertThrows FAILED\n    Location: ./dev/tests.ek9:5:3\n    Expression: riskyFunction()\n    Expected: org.ek9.lang::Exception\n    Actual: No exception was thrown\nThis structured output is captured at compile time from the AST, not generated at runtime.\n\nCUSTOM EXCEPTION TYPES\nassertThrows works with custom exception types:\n  assertThrows(ValidationError, validate(badInput))\nThe exception type must match exactly. A parent type will not catch a child type in assertThrows.\n\nCOMPILE-TIME RESTRICTION\nassertThrows is only valid in @Test programs. Using it in production code causes a compile error. This prevents test assertions from accidentally appearing in production.\n\nSee Q134 for try/catch. See Q305 for require vs assert vs throw. See Q307 for assertDoesNotThrow. See Q209 for exception testing patterns.","ek9Example":"defines module qa.errorhandling.assertthrows\n\n  defines class\n\n    <?-\n      Custom exception carrying a field name for validation errors.\n    -?>\n    ValidationError extends Exception\n      field <- String()\n\n      ValidationError()\n        ->\n          reason as String\n          fieldName as String\n        super(reason)\n        this.field :=: fieldName\n\n      field() as pure\n        <- rtn as String: field\n\n      default operator ?\n\n  defines function\n\n    <?-\n      Always throws an exception for testing purposes.\n    -?>\n    failingOperation()\n      throw Exception(\"Operation failed\")\n\n    <?-\n      Validates and throws ValidationError for bad input.\n    -?>\n    validatePositive()\n      -> number as Integer\n      <- rtn as Integer: number\n\n      if number < 0\n        ex <- ValidationError(\"Must be positive\", \"number\")\n        throw ex\n\n    <?-\n      Safe operation that does not throw.\n    -?>\n    safeAdd() as pure\n      ->\n        a as Integer\n        b as Integer\n      <- rtn as Integer: a + b\n\n  defines program\n\n    @Test\n    AssertThrowsStatementTest()\n      stdout <- Stdout()\n\n      stdout.println(\"=== assertThrows as statement ===\")\n\n      assertThrows(Exception, failingOperation())\n      stdout.println(\"Verified: failingOperation() throws Exception\")\n\n      assertThrows(ValidationError, validatePositive(-5))\n      stdout.println(\"Verified: validatePositive(-5) throws ValidationError\")\n\n    @Test\n    AssertThrowsCaptureTest()\n      stdout <- Stdout()\n\n      stdout.println(\"=== assertThrows as expression (capture) ===\")\n\n      caught <- assertThrows(Exception, failingOperation())\n      assert caught?\n      stdout.println(\"Caught reason: \" + caught.reason())\n\n      valError <- assertThrows(ValidationError, validatePositive(-10))\n      assert valError?\n      stdout.println(\"Validation field: \" + valError.field())\n      stdout.println(\"Validation reason: \" + valError.reason())","migrationContext":"Java: JUnit assertThrows(() -> code, ExceptionType.class) returns the exception. Python: pytest.raises(ExceptionType) as context manager. Rust: #[should_panic] attribute on test function (coarse). Go: manual check with recover() in test helper. Kotlin: assertThrows<ExceptionType> { code }. C#: Assert.Throws<ExceptionType>(() => code). EK9: assertThrows(ExceptionType, expression) is a built-in keyword, not a library function. Compile-time restricted to @Test programs. Structured failure output with AST-captured source location.","keywords":["assertThrows","capture","catch","diagnostic","exception","handle","structured","test","testing","verify"],"primaryTopics":[],"typicalErrors":[{"error":"E04030","correct":"assertThrows(Exception, failingOperation())","incorrect":"assertThrows(String, failingOperation())","explanation":"The first argument to assertThrows must be an Exception type or subclass. Using a non-exception type like String triggers E04030 — type must be of Exception type. Use Exception or a custom exception class that extends Exception. See ek9 -h E04030 for details."}],"companions":[]}
{"id":307,"category":"Error Handling and Exceptions","question":"How do I verify that code does not throw an exception in EK9?","url":"https://ek9.io/qa/QA0307.html","alternatePhrasings":["How does assertDoesNotThrow work in EK9?","How do I test the happy path does not throw?","What is the EK9 equivalent of JUnit assertDoesNotThrow?"],"answer":"EK9 provides assertDoesNotThrow as a built-in keyword for verifying that an expression completes without throwing any exception. It is only valid inside @Test programs.\n\nSYNTAX\nassertDoesNotThrow(expression)\nThe test passes if the expression completes normally. The test fails if any exception is thrown.\n\nUSAGE\nUse assertDoesNotThrow to verify no exception occurs:\n  assertDoesNotThrow(safeFunction())\nIf safeFunction() throws, the test fails with structured diagnostic output.\n\nFAILURE OUTPUT\nWhen assertDoesNotThrow fails, EK9 provides structured output:\n  assertDoesNotThrow FAILED\n    Location: ./dev/tests.ek9:5:3\n    Expression: riskyFunction()\n    Expected: No exception\n    Actual: org.ek9.lang::Exception\n    Message: Division by zero\nThe output includes the exception type and message, captured at compile time from the AST.\n\nWHEN TO USE\nassertDoesNotThrow is valuable when:\n- Testing boundary conditions that should not throw (edge cases).\n- Verifying error recovery leaves the system in a stable state.\n- Testing that previously throwing code has been fixed.\n- Documenting in tests that certain operations are safe.\n\nASSERTTHROWS VS ASSERTDOESNOTTHROW\nassertThrows(ExceptionType, expr): Verifies an exception IS thrown. Can capture the exception.\nassertDoesNotThrow(expr): Verifies NO exception is thrown.\nBoth are compile-time restricted to @Test programs.\n\nSee Q306 for assertThrows. See Q305 for require vs assert vs throw. See Q156 for basic assertions. See Q209 for exception testing patterns.","ek9Example":"defines module qa.errorhandling.assertdoesnot\n\n  defines function\n\n    <?-\n      A safe division that returns Result instead of throwing.\n      Used to demonstrate safe operations.\n    -?>\n    safeDivide()\n      ->\n        a as Integer\n        b as Integer\n      <-\n        rtn as Integer: 0\n\n      if b == 0\n        throw Exception(\"Division by zero\")\n      rtn: a / b\n\n    <?-\n      Safe string processing that never throws.\n    -?>\n    processName() as pure\n      -> name as String\n      <- rtn as String: `Hello, ${name}!`\n\n  defines program\n\n    @Test\n    AssertDoesNotThrowStatementTest()\n      stdout <- Stdout()\n\n      stdout.println(\"=== assertDoesNotThrow as statement ===\")\n\n      assertDoesNotThrow(processName(\"EK9\"))\n      stdout.println(\"Verified: processName does not throw\")\n\n      assertDoesNotThrow(safeDivide(10, 2))\n      stdout.println(\"Verified: safeDivide(10, 2) does not throw\")\n\n    @Test\n    AssertDoesNotThrowMultipleTest()\n      stdout <- Stdout()\n\n      stdout.println(\"=== multiple assertDoesNotThrow checks ===\")\n\n      assertDoesNotThrow(processName(\"World\"))\n      stdout.println(\"Verified: processName(\\\"World\\\") is safe\")\n\n      assertDoesNotThrow(safeDivide(100, 5))\n      stdout.println(\"Verified: safeDivide(100, 5) is safe\")\n\n      assertDoesNotThrow(safeDivide(0, 1))\n      stdout.println(\"Verified: safeDivide(0, 1) is safe\")","migrationContext":"Java: JUnit assertDoesNotThrow(() -> code) wraps Executable. Python: no built-in equivalent, just run the code and let pytest catch unexpected exceptions. Rust: no equivalent, tests fail on panic by default. Go: no equivalent, tests fail on unrecovered panic. Kotlin: assertDoesNotThrow { code } from kotlin.test. EK9: assertDoesNotThrow(expression) is a built-in keyword, compile-time restricted to @Test programs, with structured failure output including exception type and message.","keywords":["assertDoesNotThrow","catch","diagnostic","exception","handle","happy path","no throw","safe","test","testing","verify"],"primaryTopics":[],"typicalErrors":[{"error":"E08091","correct":"processName() as pure\n      -> name as String\n      <- rtn as String: `Hello, ${name}!`","incorrect":"processName() as pure\n      -> name as String\n      <- rtn as String: \"Hello!\"","explanation":"The parameter name is declared but never used in the function body. All parameters must be referenced. See ek9 -h E08091 for details."}],"companions":[]}
{"id":308,"category":"Error Handling and Exceptions","question":"How do I handle different exception types in EK9 without casting?","url":"https://ek9.io/qa/QA0308.html","alternatePhrasings":["How do I use the dispatcher pattern with exceptions?","How do I route exceptions to different handlers in EK9?","How does EK9 handle multiple exception types without instanceof?"],"answer":"EK9 uses the dispatcher pattern to route different exception types to specific handlers without casting or instanceof checks. Since EK9 has single catch per try block (always catching as base Exception), the dispatcher provides type-safe multi-type exception handling.\n\nTHE PATTERN\n1. Catch as base Exception in the try/catch block.\n2. Pass the caught exception to a dispatcher method.\n3. The dispatcher routes to the correct private overload based on actual runtime type.\n4. Each overload accesses the specific exception type's methods directly.\n\nDISPATCHER METHOD\nDeclare the dispatcher with the base Exception type:\n  private handleException() as dispatcher\n    -> ex as Exception\n    <- rtn as String: $ex\nThis is the fallback for any exception type not specifically handled.\n\nSPECIFIC OVERLOADS\nAdd private overloads for each exception type to handle:\n  private handleException()\n    -> ex as NetworkError\n    <- rtn as String: \"Network: \" + ex.host()\n  private handleException()\n    -> ex as TimeoutError\n    <- rtn as String: \"Timeout: \" + $ex.seconds() + \"s\"\nEach overload receives the correctly typed exception, no casting needed.\n\nUSAGE IN CATCH\n  try\n    riskyOperation()\n  catch\n    -> ex as Exception\n    message <- handleException(ex)\n    stderr.println(message)\nThe dispatcher automatically routes to the correct overload based on the actual exception type.\n\nWHY DISPATCHER\n- No casting: each overload receives the correct type.\n- No instanceof checks: EK9 does not have instanceof.\n- Type-safe: compiler verifies each overload's parameter type.\n- Extensible: add new exception types by adding new overloads.\n- Clean separation: each handler is a focused, single-purpose method.\n\nSee Q138 for exception subtypes. See Q134 for try/catch. See Q136 for throwing exceptions. See Q121 for the dispatcher pattern generally.","ek9Example":"defines module qa.errorhandling.exdispatcher\n\n  defines class\n\n    <?-\n      Network-related exception with host information.\n    -?>\n    NetworkError extends Exception\n      host <- String()\n\n      NetworkError()\n        ->\n          reason as String\n          host as String\n        super(reason)\n        this.host :=: host\n\n      host() as pure\n        <- rtn as String: host\n\n      default operator ?\n\n    <?-\n      Timeout exception with duration information.\n    -?>\n    TimeoutError extends Exception\n      seconds <- Integer()\n\n      TimeoutError()\n        ->\n          reason as String\n          seconds as Integer\n        super(reason)\n        this.seconds :=: seconds\n\n      seconds() as pure\n        <- rtn as Integer: seconds\n\n      default operator ?\n\n    <?-\n      Demonstrates the dispatcher pattern for exception handling.\n      Catches base Exception then dispatches to type-specific handlers.\n    -?>\n    ServiceClient\n      stderr as Stderr: Stderr()\n\n      <?-\n        Simulate an operation that can throw different exception types.\n      -?>\n      callService()\n        -> endpoint as String\n        <- rtn as String: String()\n\n        if endpoint == \"network\"\n          ex <- NetworkError(\"Connection refused\", \"api.example.com\")\n          throw ex\n        else if endpoint == \"timeout\"\n          ex <- TimeoutError(\"Request timed out\", 30)\n          throw ex\n        else if endpoint == \"generic\"\n          throw Exception(\"Unknown service error\")\n\n        rtn: \"Success from \" + endpoint\n\n      <?-\n        Try calling the service and handle any exception via dispatcher.\n      -?>\n      safeCall()\n        -> endpoint as String\n        <- rtn as String: String()\n\n        try\n          rtn: callService(endpoint)\n        catch\n          -> ex as Exception\n          rtn: handleException(ex)\n\n      <?-\n        Dispatcher: fallback for unrecognised exception types.\n      -?>\n      private handleException() as dispatcher\n        -> ex as Exception\n        <- rtn as String: \"General error: \" + ex.reason()\n\n      <?-\n        Handler for NetworkError: access host-specific information.\n      -?>\n      private handleException()\n        -> ex as NetworkError\n        <- rtn as String: `Network error on ${ex.host()}: ${ex.reason()}`\n\n      <?-\n        Handler for TimeoutError: access timeout-specific information.\n      -?>\n      private handleException()\n        -> ex as TimeoutError\n        <- rtn as String: `Timeout after ${ex.seconds()}s: ${ex.reason()}`\n\n      override operator ? as pure\n        <- rtn as Boolean: stderr?\n\n  defines program\n\n    ExceptionDispatcherDemo()\n      stdout <- Stdout()\n\n      client <- ServiceClient()\n\n      stdout.println(\"=== Dispatcher routes exceptions by type ===\")\n\n      result1 <- client.safeCall(\"ok\")\n      stdout.println(\"OK endpoint: \" + result1)\n\n      result2 <- client.safeCall(\"network\")\n      stdout.println(\"Network endpoint: \" + result2)\n\n      result3 <- client.safeCall(\"timeout\")\n      stdout.println(\"Timeout endpoint: \" + result3)\n\n      result4 <- client.safeCall(\"generic\")\n      stdout.println(\"Generic endpoint: \" + result4)","migrationContext":"Java: catch blocks with instanceof or multi-catch (catch IOException | SQLException), or visitor pattern. Python: multiple except clauses ordered by specificity. Go: errors.Is/errors.As with type switches. Rust: match on enum variants in Result. Kotlin: multiple catch blocks or when expression with is checks. EK9: dispatcher pattern eliminates casting and instanceof entirely. Single catch block delegates to dispatcher which routes by runtime type.","keywords":["casting","catch","dispatcher","exception","handle","handler","instanceof","overload","polymorphic","routing","sealed","type-safe","visitor"],"primaryTopics":[],"typicalErrors":[{"error":"E50060","correct":"ex.host()","incorrect":"ex.getHost()","explanation":"The method is named host(), not getHost(). EK9 does not follow Java getter naming conventions. See ek9 -h E50060 for details."}],"companions":[],"oracleToolHint":{"tool":"ek9_scaffold","intent":"dispatcher","description":"Oracle can generate a dispatcher for handling different exception types without casting."}}
{"id":309,"category":"Common String Operations","question":"Why does string concatenation with + cause a compiler error in EK9?","url":"https://ek9.io/qa/QA0309.html","alternatePhrasings":["What is error E11068 in EK9?","Why can't I use + to build strings in EK9?","How do I fix PREFER_STRING_INTERPOLATION error in EK9?"],"answer":"EK9 enforces string interpolation over concatenation chains. If you concatenate 3 or more parts with the + operator where the result is a String, the compiler produces error E11068: PREFER_STRING_INTERPOLATION.\n\nWHY IT IS AN ERROR\nEach + on strings calls the _add operator, creating a new intermediate String. A chain of N concatenations creates N-1 temporary objects. String interpolation compiles to a bespoke STRING_INTERPOLATION IR instruction that backends optimise to one allocation:\n  JVM: invokedynamic StringConcatFactory.makeConcatWithConstants()\n  LLVM: single allocation with pre-calculated length + memcpy\n\nBeyond performance, interpolation shows the output shape directly. Concatenation requires mentally assembling fragments separated by + operators.\n\nWHAT TRIGGERS E11068\nA chain of 3+ parts where the result type is String:\n  result <- \"Hello \" + name + \"!\"         3 parts: error\n  result <- a + \" \" + b + \" \" + c         5 parts: error\n  result <- prefix() + text + \"]\"          3 parts: error\n\nWHAT DOES NOT TRIGGER E11068\n  result <- first + second                  2 parts: allowed\n  total <- a + b + c                        numeric +: not String type\n  combined <- list1 + list2 + list3         list +: not String type\n\nHOW TO FIX\nConvert the concatenation chain to string interpolation:\n  BAD:  result <- \"Hello \" + name + \"!\"\n  GOOD: result <- `Hello ${name}!`\n\n  BAD:  result <- host + \":\" + $port\n  GOOD: result <- `${host}:${$port}`\n\n  BAD:  result <- \"(\" + $x + \", \" + $y + \")\"\n  GOOD: result <- `(${$x}, ${$y})`\n\nUse the $ operator inside ${...} to convert non-String values.\n\nSee Q174 for all string combination methods. See Q37 for string basics. See Q43 for escape sequences and literal dollar signs in interpolation.","ek9Example":"defines module qa.stringops.preferinterpolation\n\n  defines function\n\n    // Two-part concatenation is allowed\n    twoPartConcat()\n      ->\n        first as String\n        second as String\n      <- result as String: first + second\n\n    // Interpolation for 3+ parts\n    greetPerson()\n      -> name as String\n      <- result as String: `Hello ${name}!`\n\n    // Mixed types with $ operator\n    formatEndpoint()\n      ->\n        host as String\n        port as Integer\n      <- result as String: `${host}:${port}`\n\n    // Complex interpolation\n    formatCoordinate()\n      ->\n        x as Integer\n        y as Integer\n      <- result as String: `(${x}, ${y})`\n\n  defines program\n    InterpolationDemo()\n      stdout <- Stdout()\n\n      // Two-part concat is fine\n      stdout.println(twoPartConcat(\"Hello\", \"World\"))\n\n      // Interpolation for everything else\n      stdout.println(greetPerson(\"Steve\"))\n      stdout.println(formatEndpoint(\"localhost\", 8080))\n      stdout.println(formatCoordinate(10, 20))\n\n      // Numeric addition is unaffected (not String type)\n      a <- 1\n      b <- 2\n      c <- 3\n      total <- a + b + c\n      stdout.println(`Total: ${total}`)","migrationContext":"Java: no restriction on + chains (StringBuilder optimisation by javac since Java 9, invokedynamic since Java 11). Python: no restriction, but f-strings recommended by PEP 498. Rust: format!() macro preferred over + chains but not enforced. Go: no restriction, but fmt.Sprintf() recommended. JavaScript: template literals recommended over + chains by ESLint prefer-template rule but not a hard error. Kotlin: string templates recommended but + chains compile fine. Swift: string interpolation \\\\(expr) recommended but + chains compile fine, no enforcement. EK9: 3+ part + chains on String are a hard compiler error (E11068) because EK9 has a bespoke STRING_INTERPOLATION IR instruction that generates provably better code.","keywords":["E11068","PREFER_STRING_INTERPOLATION","backtick","compile","compiler","concatenation","debug","error","interpolation","performance","plus","string","swift","text"],"primaryTopics":[],"typicalErrors":[{"error":"E11068","correct":"<- result as String: `Hello ${name}!`","incorrect":"<- result as String: \"Hello \" + name + \"!\"","explanation":"Concatenating 3 or more string parts with + triggers E11068. Use backtick interpolation which compiles to a single allocation. See ek9 -h E11068 for details."},{"error":"E11068","correct":"<- result as String: `${host}:${port}`","incorrect":"<- result as String: host + \":\" + $port","explanation":"Concatenating three or more String parts with '+' triggers E11068 — use backtick interpolation `${host}:${port}`, which compiles to a single allocation. See ek9 -h E11068 for details."}],"companions":[]}
{"id":310,"category":"Code Quality","question":"How does EK9 enforce code quality at compile time?","url":"https://ek9.io/qa/QA0310.html","alternatePhrasings":["Does EK9 replace SonarQube?","How does EK9 enforce quality without external tools?","What quality gates does the EK9 compiler check?"],"answer":"EK9 enforces code quality as a mandatory compilation step. If your code compiles, it has passed ALL quality gates. There is no separate linting, SAST scanning, or code review tool needed.\n\nTHE QUALITY PYRAMID (5 LAYERS)\nLayer 1 - Grammar: dangerous constructs (break, continue, return, null, goto) removed from the language entirely.\nLayer 2 - Type system: null safety, type coercion, operator correctness, purity enforcement.\nLayer 3 - Code flow: unreachable code, uninitialised variables, guard completeness.\nLayer 4 - Security: sanitized parameters, injection detection, purity for data integrity.\nLayer 5 - Code quality: complexity, cohesion, coupling, naming, code smells, magic literals.\n\nNO-WARNINGS PHILOSOPHY\nEK9 has no warnings. Every check either passes or produces an error. This eliminates the industry pattern of accumulating thousands of ignored warnings. If the compiler is silent, every quality dimension has been satisfied.\n\nVERTICAL INTEGRATION\nTraditional projects use 15-20 separate tools: compiler, linter, SAST scanner, coverage tool, style checker, complexity analyser, dependency checker, and more. Each is optional, each has different configuration. EK9 collapses ALL of these into the compiler. One tool, absolute enforcement, no bypass.\n\nWHY THIS MATTERS FOR TEAMS\nCode review arguments about style, complexity, and naming disappear. The compiler has already decided. Teams focus on architecture and design instead of bikeshedding over formatting.\n\nSee Q311 for the full catalog of quality checks. See Q312 for complexity metrics. See Q313 for code smell detection. See Q321 for the quality report dashboard. See Q144 for how eliminating break/continue/return is part of the quality pyramid. See Q625 for why EK9 enforces a standard code format.\nSee Q693 for captured operator returns. See Q695 for named constants. See Q696 for complexity limits.\nSee Q728 for nesting depth boundary. See Q730 for data clump boundary. See Q731 for type traversal boundary. See Q756 for the fuzzing quality loop.","ek9Example":"defines module qa.codequality.compiletime\n\n  defines function\n\n    <?-\n      A pure function that demonstrates passing all quality gates.\n      If this compiles, it satisfies naming, complexity, coupling,\n      and every other quality check automatically.\n    -?>\n    calculateDiscount() as pure\n      ->\n        originalPrice as Float\n        discountPercent as Float\n      <-\n        discountedPrice as Float: originalPrice\n\n      minimumDiscount <- 0.0\n      maximumDiscount <- 100.0\n\n      if discountPercent > minimumDiscount and discountPercent <= maximumDiscount\n        reduction <- originalPrice * discountPercent / maximumDiscount\n        discountedPrice: originalPrice - reduction\n\n  defines program\n\n    QualityAtCompileTimeDemo()\n      stdout <- Stdout()\n\n      fullPrice <- 99.99\n      tenPercent <- 10.0\n      salePrice <- calculateDiscount(fullPrice, tenPercent)\n\n      stdout.println(`Original: ${fullPrice}`)\n      stdout.println(`Discounted: ${salePrice}`)","migrationContext":"Java: SonarQube, Checkstyle, PMD, SpotBugs, JaCoCo, ErrorProne (all separate, all optional). Python: pylint, flake8, mypy, bandit, radon (all separate, all optional). Rust: clippy (built-in but warnings only, can be ignored). Go: go vet + staticcheck (some built-in, still separate tools). JavaScript: ESLint, TypeScript, SonarJS (all separate, all optional). EK9: ALL quality checks in the compiler, mandatory, no external tools, no configuration, no bypass.","keywords":["clean-code","compile","enforce","gate","integration","mandatory","metric","pyramid","quality","sonarqube","vertical","warnings"],"primaryTopics":["code quality","quality checks","static analysis"],"typicalErrors":[{"error":"E50001","correct":"salePrice <- calculateDiscount(fullPrice, tenPercent)","incorrect":"salePriceXYZ <- calculateDiscount(fullPrice, tenPercent)","explanation":"Renaming the variable means later references to 'salePrice' become unresolved, triggering E50001. See ek9 -h E50001 for details."},{"error":"E50001","correct":"salePrice <- calculateDiscount(fullPrice, tenPercent)","incorrect":"calculateDiscount(fullPrice, tenPercent)","explanation":"Removing the variable declaration means later references to the variable become unresolved, triggering E50001. See ek9 -h E50001 for details."}],"companions":[]}
{"id":311,"category":"Code Quality","question":"What code quality checks does the EK9 compiler perform?","url":"https://ek9.io/qa/QA0311.html","alternatePhrasings":["What are all the EK9 quality error codes?","What does EK9 check for code quality?","List all EK9 code quality rules"],"answer":"The EK9 compiler performs over 30 distinct code quality checks, each with a specific error code. These are grouped into families.\n\nCOMPLEXITY (E11010-E11012, E11020-E11021)\nE11010: Cyclomatic complexity exceeds threshold (default 45).\nE11011: Nesting depth exceeds threshold (default 6 levels).\nE11012: Statement count exceeds threshold.\nE11020: Combined complexity product exceeds threshold.\nE11021: Cognitive complexity exceeds threshold (default 35).\n\nCOHESION AND COUPLING (E11014-E11017)\nE11014: Lack of Cohesion (LCOM4) exceeds threshold.\nE11015: Efferent coupling (Ce) exceeds threshold.\nE11016: Module-level coupling exceeds threshold.\nE11017: Module-level cohesion below threshold.\n\nINHERITANCE (E11019)\nE11019: Inheritance depth exceeds threshold (class/trait 4, function 3, record 2).\n\nRESPONSIBILITY (E11022-E11025)\nE11022: Class should be a component (too many injected dependencies).\nE11023: God class detected (too many methods and fields).\nE11024: Missing delegation (class implements trait without 'by' delegation).\nE11025: Hybrid aggregate (mixes data and behaviour excessively).\n\nNAMING (E11030-E11032)\nE11030: Type name does not follow conventions.\nE11031: Non-descriptive variable name (temp, data, obj, etc.).\nE11032: Variable name shadows operator keyword.\n\nINJECTION (E11040)\nE11040: Component has too many injection fields.\n\nDISCARDED RETURNS (E11050-E11055)\nE11050: Computational operator result discarded.\nE11051: Pure function return value discarded.\nE11052: Result or Optional return value not checked.\nE11055: Constructor result discarded (always wrong).\n\nCODE SMELLS (E11053-E11054, E11061-E11062, E11068)\nE11053: Data clump detected (extract a record).\nE11054: Law of Demeter violation (train wreck).\nE11061: Named argument required at call site.\nE11062: Named argument position mismatch.\nE11068: String concatenation should use interpolation.\n\nMAGIC LITERALS (E11064-E11067)\nE11064: Magic literal in comparison.\nE11065: Repeated literal (3+ per file or 4+ per module).\nE11066: Constant not in dedicated constant block.\nE11067: Constant naming convention violation.\n\nSELF-OPERATIONS (E08080-E08081)\nE08080: Self-assignment is pointless.\nE08081: Self-comparison always produces a constant result.\n\nFLOW CONDITIONS (E08082-E08089)\nE08082: Constant comparison (both operands are literals).\nE08083: Constant arithmetic (both operands are literals).\nE08084: Redundant boolean comparison (comparing boolean with true/false).\nE08085: Logical tautology (expression always true or always false).\nE08086: Condition always true (flow-sensitive dead code).\nE08087: Condition always false (flow-sensitive dead code).\nE08088: Redundant isSet check (variable known to be set).\nE08089: Never-set isSet check (variable known to be unset).\n\nUNUSED (E08091, E11018)\nE08091: Unused parameter detected.\nE11018: Unused closure capture.\n\nEMPTY CHECKS (E08092-E08093)\nE08092: Redundant empty check (collection known to be non-empty).\nE08093: Never-empty check (collection known to be empty).\n\nSee Q312 for complexity detail. See Q313 for code smells. See Q314 for cohesion and coupling. See Q315 for inheritance depth. See Q316 for discarded returns. See Q317 for magic literals. See Q318 for self-operations. See Q319 for unused parameters. See Q290 for naming rules. See Q557 for dead code detection. See Q558 for tautological conditions. See Q559 for redundant isSet. See Q247 for reading error messages. See Q248 for error code lookup.","ek9Example":"defines module qa.codequality.catalog\n\n  defines function\n\n    <?-\n      Demonstrates a function that passes all quality checks.\n      Every check in the catalog has been satisfied for this to compile.\n    -?>\n    gradeScore() as pure\n      -> score as Integer\n      <- grade as String: \"F\"\n\n      excellentThreshold <- 90\n      goodThreshold <- 70\n      passThreshold <- 50\n\n      if score >= excellentThreshold\n        grade: \"A\"\n      else if score >= goodThreshold\n        grade: \"B\"\n      else if score >= passThreshold\n        grade: \"C\"\n\n  defines program\n\n    QualityChecksCatalogDemo()\n      stdout <- Stdout()\n\n      testScore <- 85\n      result <- gradeScore(testScore)\n      stdout.println(`Score ${testScore} earns grade: ${result}`)","migrationContext":"Java: SonarQube has 600+ rules but all optional. PMD has 300+ rules but all configurable. EK9: 30+ mandatory rules, zero configuration, cannot be disabled. Rust: clippy has 500+ lints but most are allow-by-default. Go: go vet checks a small subset, staticcheck adds more but all optional. Python: pylint has 200+ checks but all configurable. EK9 is the only language where ALL quality checks are mandatory compiler errors.","keywords":["E08080","E11010","E11014","E11019","E11030","E11050","E11064","catalog","checks","clean-code","codes","compile","error","metric","quality","rules"],"primaryTopics":[],"typicalErrors":[{"error":"E50001","correct":"testScore <- 85","incorrect":"testScoreXYZ <- 85","explanation":"Renaming the variable means later references to 'testScore' become unresolved, triggering E50001. See ek9 -h E50001 for details."},{"error":"E50001","correct":"result <- gradeScore(testScore)","incorrect":"resultXYZ <- gradeScore(testScore)","explanation":"Renaming the variable means later references to 'result' become unresolved, triggering E50001. See ek9 -h E50001 for details."}],"companions":[]}
{"id":312,"category":"Code Quality","question":"How does EK9 measure complexity?","url":"https://ek9.io/qa/QA0312.html","alternatePhrasings":["What complexity metrics does EK9 use?","How does EK9 calculate cyclomatic complexity?","What is cognitive complexity in EK9?"],"answer":"EK9 measures complexity using five metrics, all enforced at compile time. Exceeding any threshold is a compilation error, not a warning.\n\nCYCLOMATIC COMPLEXITY (E11010)\nMeasures the number of independent paths through a function. Each if, else if, while, for, switch case, and guard adds one. Threshold: 45. Based on McCabe 1976 research showing defect rate increases sharply above this level.\n\nNESTING DEPTH (E11011)\nMeasures the deepest level of nesting in a function. Each nested if, for, while, switch, or try adds one level. Threshold: 6. Deep nesting correlates with bugs because humans lose track of context beyond 3-4 levels.\n\nSTATEMENT COUNT (E11012)\nCounts executable statements in a function. Long functions are harder to understand, test, and modify. The threshold encourages decomposition into smaller, focused functions.\n\nCOMBINED COMPLEXITY (E11020)\nA product formula combining cyclomatic complexity, nesting depth, and statement count. This catches functions that are individually below each threshold but collectively too complex. A function with 40 cyclomatic, 5 nesting, and high statements can still be flagged.\n\nCOGNITIVE COMPLEXITY (E11021)\nMeasures how hard code is to understand, not just how many paths exist. Cognitive complexity penalises nesting more heavily than flat structures. A deeply nested if-else chain scores higher than the same logic refactored into flat guard expressions. Threshold: 35.\n\nWHAT ADDS COMPLEXITY\nEach of these adds to complexity:\n- if/else if/else branches\n- for/while loops\n- switch cases\n- try/catch handlers\n- guard expressions (but less than nested ifs)\n- Boolean operators in conditions\n\nDECOMPOSITION PATTERN\nWhen a function is too complex, extract helper functions. Each extracted function has its own complexity budget. This is exactly the decomposition that EK9 philosophy encourages.\n\nSee Q311 for the full quality checks catalog. See Q313 for code smell detection. See Q146 for decomposition over return. See Q630 for identifying hot methods via profiling.","ek9Example":"defines module qa.codequality.complexity\n\n  defines function\n\n    <?-\n      A well-decomposed function stays within complexity thresholds.\n      Each helper handles one concern with low individual complexity.\n    -?>\n    isWeekday() as pure\n      -> dayNumber as Integer\n      <- rtn as Boolean: false\n\n      mondayIndex <- 1\n      fridayIndex <- 5\n      if dayNumber >= mondayIndex and dayNumber <= fridayIndex\n        rtn: true\n\n    describeDayType() as pure\n      -> dayNumber as Integer\n      <- description as String: \"weekend\"\n\n      if isWeekday(dayNumber)\n        description: \"weekday\"\n\n    formatDayReport() as pure\n      ->\n        dayName as String\n        dayNumber as Integer\n      <-\n        report as String: \"\"\n\n      dayType <- describeDayType(dayNumber)\n      report: `${dayName} is a ${dayType}`\n\n  defines program\n\n    ComplexityMetricsDemo()\n      stdout <- Stdout()\n\n      mondayNumber <- 1\n      saturdayNumber <- 6\n\n      mondayReport <- formatDayReport(\"Monday\", mondayNumber)\n      saturdayReport <- formatDayReport(\"Saturday\", saturdayNumber)\n\n      stdout.println(mondayReport)\n      stdout.println(saturdayReport)","migrationContext":"Java: SonarQube measures cyclomatic and cognitive complexity but as optional warnings. PMD has CyclomaticComplexity rule but configurable threshold. Rust: no built-in complexity measurement, clippy has cognitive_complexity lint (allow by default). Go: gocyclo is a third-party tool, not built-in. Python: radon measures complexity but is a separate optional tool. EK9: all five complexity metrics built into the compiler as mandatory errors with fixed thresholds.","keywords":["E11010","E11011","E11020","E11021","clean-code","cognitive","complexity","cyclomatic","depth","mccabe","metric","metrics","nesting","quality","threshold"],"primaryTopics":["complexity","cyclomatic complexity"],"typicalErrors":[{"error":"E50001","correct":"mondayNumber <- 1","incorrect":"mondayNumberXYZ <- 1","explanation":"Renaming the variable means later references to 'mondayNumber' become unresolved, triggering E50001. See ek9 -h E50001 for details."},{"error":"E50001","correct":"mondayReport <- formatDayReport(\"Monday\", mondayNumber)","incorrect":"mondayReportXYZ <- formatDayReport(\"Monday\", mondayNumber)","explanation":"Renaming the variable means later references become unresolved, triggering E50001. See ek9 -h E50001 for details."}],"companions":[]}
{"id":313,"category":"Code Quality","question":"How does EK9 detect code smells?","url":"https://ek9.io/qa/QA0313.html","alternatePhrasings":["What code smells does EK9 catch?","How does EK9 detect god classes and data clumps?","Does EK9 enforce the Law of Demeter?"],"answer":"EK9 detects several categories of code smell at compile time and rejects code that exhibits them.\n\nRESPONSIBILITY SMELLS (E11022-E11025)\nE11022 Class should be component: When a class has too many injected dependencies, it should be restructured as a component.\nE11023 God class: A class with too many methods and fields is doing too much. Split into smaller, focused classes.\nE11024 Missing delegation: A class implements a trait but does not use 'by' delegation. Manual forwarding is error-prone.\nE11025 Hybrid aggregate: A type mixes data storage and complex behaviour. Separate into a record (data) and a class (behaviour).\n\nDATA CLUMPS (E11053)\nWhen the same group of parameters appears together in multiple function signatures, extract them into a record. Records give the group a name and make the code self-documenting.\n\nLAW OF DEMETER (E11054)\nChained method calls like a.getB().getC().doSomething() violate the Law of Demeter. Each object should only talk to its immediate collaborators. EK9 detects excessive chaining and flags it.\n\nSTRING CONCATENATION (E11068)\nUsing the + operator to build strings with three or more parts should use string interpolation instead. Interpolation is clearer, faster, and avoids intermediate string allocations.\n\nNAMED ARGUMENTS (E11061-E11062)\nCalls with many positional arguments are hard to read. EK9 requires named arguments beyond a threshold to prevent parameter order mistakes.\n\nSee Q311 for the full quality checks catalog. See Q312 for complexity metrics. See Q314 for cohesion and coupling. See Q109 for composition patterns. See Q267 for anti-patterns including god classes.","ek9Example":"defines module qa.codequality.smells\n\n  defines record\n\n    <?-\n      Extract related fields into a record to avoid data clumps.\n      Instead of passing street, city, postcode separately everywhere,\n      group them into an Address record.\n    -?>\n    Address\n      street as String: String()\n      city as String: String()\n      postcode as String: String()\n\n      Address()\n        ->\n          street as String\n          city as String\n          postcode as String\n        this.street :=? street\n        this.city :=? city\n        this.postcode :=? postcode\n\n      override operator ? as pure\n        <- rtn as Boolean: street? and city? and postcode?\n\n      operator $ as pure\n        <- rtn as String: `${street}, ${city} ${postcode}`\n\n  defines function\n\n    <?-\n      Uses the Address record instead of three separate parameters.\n      This eliminates the data clump smell.\n    -?>\n    formatMailingLabel() as pure\n      ->\n        recipientName as String\n        recipientAddress as Address\n      <-\n        label as String: `${recipientName}\\n${recipientAddress}`\n\n  defines program\n\n    CodeSmellsDemo()\n      stdout <- Stdout()\n\n      homeAddress <- Address(\"10 High Street\", \"London\", \"SW1A 1AA\")\n      mailingLabel <- formatMailingLabel(\"Jane Smith\", homeAddress)\n      stdout.println(mailingLabel)","migrationContext":"Java: SonarQube detects god classes, data clumps, and feature envy but as optional warnings. PMD has GodClass and LawOfDemeter rules but configurable. Rust: clippy has no code smell detection beyond simple patterns. Go: no code smell detection built-in. Python: pylint detects some smells but all optional. EK9: code smell detection built into the compiler as mandatory errors.","keywords":["E11022","E11053","E11068","class","clean-code","clump","delegation","demeter","god","hybrid","interpolation","metric","quality","responsibility","smell"],"primaryTopics":[],"typicalErrors":[{"error":"E11068","correct":"        label as String: `${recipientName}\\n${recipientAddress}`","incorrect":"        label as String: recipientName + \"\\n\" + $recipientAddress","explanation":"Building a String from three or more parts with + should use interpolation; `recipientName + \"\\n\" + $recipientAddress` triggers E11068 - use a backtick `${...}` template. See ek9 -h E11068 for details."},{"error":"E50001","correct":"mailingLabel <- formatMailingLabel(\"Jane Smith\", homeAddress)","incorrect":"mailingLabelXYZ <- formatMailingLabel(\"Jane Smith\", homeAddress)","explanation":"Renaming the variable means later references to 'mailingLabel' become unresolved, triggering E50001. See ek9 -h E50001 for details."}],"companions":[]}
{"id":314,"category":"Code Quality","question":"How does EK9 measure cohesion and coupling?","url":"https://ek9.io/qa/QA0314.html","alternatePhrasings":["What is LCOM4 in EK9?","How does EK9 detect tightly coupled modules?","Does EK9 measure efferent coupling?"],"answer":"EK9 measures both cohesion and coupling at class and module level, using established software engineering metrics.\n\nLACK OF COHESION - LCOM4 (E11014)\nLCOM4 counts the number of connected components in a class. A connected component is a group of methods that share fields. A perfectly cohesive class has LCOM4 of 1 (all methods use overlapping fields). Higher values mean the class should be split. Thresholds: 8 for classes, 10 for components. Built-in types are excluded from this check.\n\nEFFERENT COUPLING - Ce (E11015)\nEfferent coupling counts how many other types a class depends on. High Ce means a class is tightly coupled to many other types and will break when any of them change. Thresholds: 8 for records, 12 for classes, 15 for components. Built-in types (String, Integer, List, etc.) are excluded because depending on standard library types is normal.\n\nMODULE COUPLING (E11016)\nCounts how many other modules a module depends on. High module coupling means changes ripple across module boundaries. Threshold: 10 external module dependencies.\n\nMODULE COHESION (E11017)\nMeasures how related the contents of a module are. Low module cohesion suggests the module is a grab-bag of unrelated functionality and should be split.\n\nCHIDAMBER AND KEMERER EVIDENCE\nThese metrics come from Chidamber and Kemerer (1994) research on object-oriented metrics. Their study of C++ projects showed that classes with high LCOM and high coupling had significantly more defects.\n\nSee Q311 for the full quality checks catalog. See Q312 for complexity metrics. See Q315 for inheritance depth.\nSee Q694 for named arguments pattern. See Q698 for excessive Boolean params.","ek9Example":"defines module qa.codequality.cohesion\n\n  defines class\n\n    <?-\n      A cohesive class where all methods share fields.\n      LCOM4 is 1 because every method uses the shared state.\n    -?>\n    TemperatureConverter\n      celsiusReading as Float: Float()\n\n      TemperatureConverter()\n        -> initialCelsius as Float\n        this.celsiusReading :=? initialCelsius\n\n      celsius() as pure\n        <- rtn as Float: celsiusReading\n\n      toFahrenheit() as pure\n        <- rtn as Float: Float()\n\n        conversionFactor <- 9.0 / 5.0\n        freezingOffset <- 32.0\n        rtn: celsiusReading * conversionFactor + freezingOffset\n\n      toKelvin() as pure\n        <- rtn as Float: Float()\n\n        kelvinOffset <- 273.15\n        rtn: celsiusReading + kelvinOffset\n\n      override operator ? as pure\n        <- rtn as Boolean: celsiusReading?\n\n      operator $ as pure\n        <- rtn as String: `${celsiusReading}C`\n\n  defines program\n\n    CohesionCouplingDemo()\n      stdout <- Stdout()\n\n      boilingPoint <- 100.0\n      reading <- TemperatureConverter(boilingPoint)\n\n      stdout.println(`Celsius: ${reading.celsius()}`)\n      stdout.println(`Fahrenheit: ${reading.toFahrenheit()}`)\n      stdout.println(`Kelvin: ${reading.toKelvin()}`)","migrationContext":"Java: SonarQube measures LCOM4 and coupling but as informational metrics, not enforcement. JDepend measures package coupling but is a separate tool. Rust: no cohesion or coupling metrics built-in or in clippy. Go: no cohesion or coupling metrics. Python: pylint has no coupling metrics. C++: CppDepend measures metrics but is a commercial separate tool. EK9: LCOM4, efferent coupling, and module coupling are mandatory compiler errors with fixed thresholds.","keywords":["E11014","E11015","E11016","LCOM4","chidamber","clean-code","cohesion","component","connected","coupling","efferent","kemerer","metric","migrate","module","quality"],"primaryTopics":[],"typicalErrors":[{"error":"E50001","correct":"reading <- TemperatureConverter(boilingPoint)","incorrect":"readingXYZ <- TemperatureConverter(boilingPoint)","explanation":"Renaming the variable means later references to 'reading' become unresolved, triggering E50001. See ek9 -h E50001 for details."},{"error":"E50001","correct":"boilingPoint <- 100.0","incorrect":"boilingPointXYZ <- 100.0","explanation":"Renaming the variable means later references to 'boilingPoint' become unresolved, triggering E50001. See ek9 -h E50001 for details."}],"companions":[]}
{"id":315,"category":"Code Quality","question":"Why does EK9 limit inheritance depth?","url":"https://ek9.io/qa/QA0315.html","alternatePhrasings":["What is the maximum inheritance depth in EK9?","How does E11019 work?","Why can I only extend 4 levels deep in EK9?"],"answer":"EK9 limits inheritance depth to prevent the fragile base class problem and deep hierarchies that become impossible to reason about.\n\nTHRESHOLDS (E11019)\nClass and trait: maximum 4 levels of inheritance.\nFunction: maximum 3 levels.\nRecord: maximum 2 levels.\n\nRecords have the strictest limit because they represent data. Data types rarely need deep hierarchies. Functions have a moderate limit. Classes and traits have the most generous limit, but still capped.\n\nTHE FRAGILE BASE CLASS PROBLEM\nWhen a class inherits through many levels, changes to any ancestor can break descendants in unexpected ways. A change to a method in level 1 cascades through levels 2, 3, 4, and beyond. The developer modifying level 1 cannot predict the impact on level 5.\n\nJAVA AWT/SWING CAUTIONARY TALE\nJava AWT and Swing have inheritance depths of 6-8 levels. Component to Container to JComponent to JPanel to custom panels. This made Swing notoriously difficult to customise and debug. Any override had to account for behaviour at every level of the hierarchy.\n\nUSE DELEGATION INSTEAD\nWhen you hit the inheritance depth limit, use the 'by' delegation keyword. Delegation composes behaviour without creating deep hierarchies. The delegating class controls exactly which methods are forwarded.\n\nSee Q109 for composition with delegation. See Q311 for the full quality checks catalog. See Q212 for composition over inheritance.","ek9Example":"defines module qa.codequality.inheritance\n\n  defines class\n\n    <?-\n      A shallow, well-structured hierarchy.\n      Level 0: Shape (abstract base)\n      Level 1: Polygon (concrete with delegation-ready design)\n    -?>\n    Shape as abstract\n      shapeName as String: String()\n\n      Shape()\n        -> shapeName as String\n        this.shapeName :=? shapeName\n\n      name() as pure\n        <- rtn as String: shapeName\n\n      area() as pure abstract\n        <- rtn as Float?\n\n      override operator ? as pure\n        <- rtn as Boolean: shapeName?\n\n      operator $ as pure\n        <- rtn as String: shapeName\n\n    <?-\n      Level 1 of inheritance.\n      Keeps the hierarchy shallow and focused.\n    -?>\n    Rectangle is Shape\n      rectangleWidth as Float: Float()\n      rectangleHeight as Float: Float()\n\n      Rectangle()\n        ->\n          width as Float\n          height as Float\n        super(\"Rectangle\")\n        this.rectangleWidth :=? width\n        this.rectangleHeight :=? height\n\n      override area() as pure\n        <- rtn as Float: rectangleWidth * rectangleHeight\n\n      override operator ? as pure\n        <- rtn as Boolean: rectangleWidth? and rectangleHeight?\n\n      override operator $ as pure\n        <- rtn as String: `Rectangle(${rectangleWidth} x ${rectangleHeight})`\n\n  defines program\n\n    InheritanceDepthDemo()\n      stdout <- Stdout()\n\n      width <- 5.0\n      height <- 3.0\n      rect <- Rectangle(width, height)\n\n      stdout.println(`Shape: ${rect}`)\n      stdout.println(`Area: ${rect.area()}`)","migrationContext":"Java: no inheritance depth limit, AWT/Swing goes 6-8 deep, frameworks encourage deep hierarchies. Python: no inheritance depth limit, multiple inheritance can create diamond depth issues. C++: no inheritance depth limit, virtual inheritance adds complexity. Rust: no inheritance at all (trait composition only). Go: no inheritance (struct embedding only). Kotlin: no depth limit but sealed classes limit hierarchy breadth. EK9: hard limits (class 4, function 3, record 2) enforced by compiler.","keywords":["AWT","E11019","Swing","base","class","clean-code","delegation","depth","fragile","hierarchy","inheritance","limit","metric","migrate","quality"],"primaryTopics":[],"typicalErrors":[{"error":"E11051","correct":"stdout.println(`Area: ${rect.area()}`)","incorrect":"rect.area()","explanation":"Calling the pure area() method as a bare statement discards its Float return value, which EK9 forbids - capture or use the result. See ek9 -h E11051 for details."},{"error":"E08090","correct":"width <- 5.0\n      height <- 3.0\n      rect <- Rectangle(width, height)","incorrect":"unusedWidth <- 5.0\n      rect <- Rectangle(5.0, 3.0)","explanation":"Declaring a variable that is never referenced is dead code. Every variable must be used. See ek9 -h E08090 for details."}],"companions":[]}
{"id":316,"category":"Code Quality","question":"How does EK9 detect discarded return values?","url":"https://ek9.io/qa/QA0316.html","alternatePhrasings":["What happens if I ignore a function return value in EK9?","Does EK9 have must_use like Rust?","Why does EK9 require capturing operator results?"],"answer":"EK9 detects four categories of discarded return values and rejects code that ignores them.\n\nDISCARDED CONSTRUCTOR (E11055)\nA constructor creates an object. Calling a constructor as a bare statement and discarding the result is always either a bug (forgot the assignment) or a misuse of constructors. If you want side effects without creating an object, use a function. This was discovered by the EK9 fuzzer: removing an assignment from valid code creates dead constructor calls that crash later compilation phases.\n\nCOMPUTATIONAL OPERATORS (E11050)\nOperators like +, -, *, / produce new values. Calling them without capturing the result is always a bug. The operator did work but the result was thrown away. Example: calling 'list + item' without assigning the new list means the addition had no effect.\n\nPURE FUNCTION RETURNS (E11051)\nA pure function has no side effects. If you call a pure function and discard the return value, the call had no observable effect. This is dead code. EK9 flags it because it usually means the developer forgot to capture the result or called the wrong function.\n\nRESULT AND OPTIONAL (E11052)\nResult and Optional return types MUST be checked. These types exist specifically to force the caller to handle success/failure or presence/absence. Discarding them defeats their entire purpose. This is equivalent to catching an exception and ignoring it.\n\nRUST ANALOGY\nRust has the #[must_use] attribute that warns when a return value is ignored. EK9 goes further: it is automatic for all constructors, all pure functions, all computational operators, and all Result/Optional types. No annotation needed, no way to suppress.\n\nSee Q311 for the full quality checks catalog. See Q48 for Result type. See Q47 for Optional type. See Q50 for function return values. See Q641 for pure function return capture patterns.\nSee Q693 for captured operator returns. See Q696 for complexity within limits. See Q700 for parameter count limits.\nSee Q732 for discarded return boundary examples.","ek9Example":"defines module qa.codequality.discarded\n\n  defines function\n\n    <?-\n      Pure function: its return value MUST be captured.\n      Calling this without capturing would trigger E11051.\n    -?>\n    doubleAmount() as pure\n      -> amount as Integer\n      <- rtn as Integer: amount * 2\n\n    <?-\n      Another pure function demonstrating the pattern.\n    -?>\n    formatCurrency() as pure\n      -> amount as Float\n      <- rtn as String: `\\$${amount}`\n\n  defines program\n\n    DiscardedReturnsDemo()\n      stdout <- Stdout()\n\n      // Correct: capturing the return value\n      originalAmount <- 50\n      doubled <- doubleAmount(originalAmount)\n      stdout.println(`Doubled: ${doubled}`)\n\n      // Correct: using the return value in an expression\n      price <- 29.99\n      formatted <- formatCurrency(price)\n      stdout.println(`Price: ${formatted}`)\n\n      // Correct: using operator result\n      items <- List() of String\n      updatedItems <- items + \"first\"\n      stdout.println(`Items: ${updatedItems.length()}`)","migrationContext":"Java: return values can always be silently discarded, no compiler enforcement. Rust: #[must_use] attribute warns but can be suppressed with let _ = expr. Go: return values can be discarded with _ blank identifier. Python: return values always silently discarded. C++: [[nodiscard]] attribute warns but can be cast to void. Kotlin: return values silently discarded. Swift: @discardableResult annotation opts specific functions into allowing discard (default is warn), can be silenced with _ = expr. EK9: automatic must-use for all pure functions, computational operators, and Result/Optional, cannot be suppressed.","keywords":["E11050","E11051","E11052","E11055","absent","capture","clean-code","code","constructor","dead","discard","discardableResult","error","guard","immutable","metric","migrate","must_use","ok","operator","optional","pure","quality","result","return","safe","side-effect","swift"],"primaryTopics":[],"typicalErrors":[{"error":"E50001","correct":"doubled <- doubleAmount(originalAmount)","incorrect":"doubledXYZ <- doubleAmount(originalAmount)","explanation":"Renaming the variable means later references to 'doubled' become unresolved, triggering E50001. See ek9 -h E50001 for details."},{"error":"E50001","correct":"updatedItems <- items + \"first\"","incorrect":"updatedItemsXYZ <- items + \"first\"","explanation":"Renaming the variable means later references to 'updatedItems' become unresolved, triggering E50001. See ek9 -h E50001 for details."}],"companions":[]}
{"id":317,"category":"Code Quality","question":"How does EK9 detect magic literals?","url":"https://ek9.io/qa/QA0317.html","alternatePhrasings":["What are magic numbers in EK9?","Why does EK9 reject literal values in comparisons?","How do I fix E11064 magic literal error?"],"answer":"EK9 detects magic literals using a two-tier system. Magic literals are unnamed numeric or string values embedded directly in code. They obscure intent and make code harder to maintain.\n\nTIER 1: COMPARISON LITERALS (E11064)\nAny literal value used in a comparison is always flagged. Comparisons are decision points where the meaning of the value matters most. Instead of 'if age >= 18', use a named constant 'if age >= legalAdultAge'. This applies to ==, <>, <, >, <=, >=, and <=>, contains, and matches.\n\nTIER 2: REPEATED LITERALS (E11065)\nA literal that appears 3 or more times in the same file, or 4 or more times in the same module, is flagged. Repetition means the value has significance worth naming. Exempt from duplication checking: Boolean values, single characters, regular expressions, 0 and 1, and empty string.\n\nCONSTANT ORGANISATION (E11066-E11067)\nE11066: Constants should be defined in a dedicated constant block, not scattered through code.\nE11067: Constant names should follow naming conventions (UPPER_CASE or descriptive camelCase).\n\nEXEMPTIONS\nThe following contexts are exempt from magic literal detection: data definitions, fixture/test data, template output, and constant initialisation. This prevents false positives when defining legitimate data structures.\n\nNAMED CONSTANTS PATTERN\nDefine meaningful names at the top of the function or in a constants block:\n  maximumRetries <- 3\n  timeoutSeconds <- 30\n  minimumPasswordLength <- 8\nThe name documents WHY this value was chosen.\n\nSee Q311 for the full quality checks catalog. See Q140 for defining constants. See Q141 for constant immutability.","ek9Example":"defines module qa.codequality.magic\n\n  defines function\n\n    <?-\n      Demonstrates using named constants instead of magic literals.\n      Every threshold has a meaningful name that documents its purpose.\n    -?>\n    classifyTemperature() as pure\n      -> temperatureCelsius as Float\n      <- classification as String: \"moderate\"\n\n      freezingPoint <- 0.0\n      coldThreshold <- 10.0\n      hotThreshold <- 30.0\n\n      if temperatureCelsius <= freezingPoint\n        classification: \"freezing\"\n      else if temperatureCelsius <= coldThreshold\n        classification: \"cold\"\n      else if temperatureCelsius >= hotThreshold\n        classification: \"hot\"\n\n    <?-\n      Named constants make password validation self-documenting.\n    -?>\n    isPasswordStrong() as pure\n      -> password as String\n      <- rtn as Boolean: false\n\n      minimumLength <- 8\n      if length password >= minimumLength\n        rtn: true\n\n  defines program\n\n    MagicLiteralsDemo()\n      stdout <- Stdout()\n\n      currentTemp <- 25.0\n      weatherReport <- classifyTemperature(currentTemp)\n      stdout.println(`Temperature ${currentTemp}C is ${weatherReport}`)\n\n      testPassword <- \"SecurePass123\"\n      strongEnough <- isPasswordStrong(testPassword)\n      stdout.println(`Password strong: ${strongEnough}`)","migrationContext":"Java: SonarQube has MagicNumber rule but optional and configurable. Checkstyle MagicNumber only catches numerics, not strings. Rust: clippy has no magic number detection. Go: no magic number detection. Python: pylint has magic-value-comparison but optional. C++: no standard magic number detection. EK9: two-tier magic literal detection (comparison always, duplication 3+/file), mandatory compiler error, covers both numbers and strings.","keywords":["E11064","E11065","clean-code","comparison","constant","literal","magic","metric","named","number","quality","repeated","value"],"primaryTopics":[],"typicalErrors":[{"error":"E50001","correct":"weatherReport <- classifyTemperature(currentTemp)","incorrect":"weatherReportXYZ <- classifyTemperature(currentTemp)","explanation":"Renaming the variable means later references to 'weatherReport' become unresolved, triggering E50001. See ek9 -h E50001 for details."},{"error":"E11064","correct":"minimumLength <- 8\n      if length password >= minimumLength","incorrect":"if length password >= 8","explanation":"Using a raw literal 8 in a comparison is a magic literal. Name it minimumLength to document why this value was chosen. See ek9 -h E11064 for details."}],"companions":[]}
{"id":318,"category":"Code Quality","question":"How does EK9 detect self-assignment and self-comparison?","url":"https://ek9.io/qa/QA0318.html","alternatePhrasings":["What is E08080 self-assignment in EK9?","How does EK9 catch copy-paste errors?","What does E08081 self-comparison mean?"],"answer":"EK9 detects two categories of self-referential operations that are always programming errors.\n\nSELF-ASSIGNMENT (E08080)\nAssigning a variable to itself is always pointless. The value does not change. This typically happens from copy-paste errors where the developer duplicated a line and forgot to change one side. Example: 'name: name' assigns name to itself.\n\nSELF-COMPARISON (E08081)\nComparing a variable to itself always produces a constant result. 'x == x' is always true. 'x <> x' is always false. 'x < x' is always false. These are never intentional and indicate a copy-paste error where the developer meant to compare two different variables.\n\nFLAGGED OPERATORS\nSelf-comparison is detected for all comparison and containment operators: ==, <>, <, >, <=, >=, <=>, <~>, contains, and matches. Each of these produces a constant result when both sides are the same variable.\n\nCOPY-PASTE ERROR DETECTION\nThese checks exist specifically to catch copy-paste errors. When duplicating code, it is easy to write 'if price == price' instead of 'if price == discountedPrice'. Without this check, the bug silently evaluates to true and the discount logic is bypassed.\n\nSee Q311 for the full quality checks catalog. See Q239 for comparison operators. See Q637 for safe comparison patterns with distinct variable names.","ek9Example":"defines module qa.codequality.selfops\n\n  defines function\n\n    <?-\n      Correct comparisons use different variables on each side.\n      The compiler would reject x == x or x: x.\n    -?>\n    findCheaper() as pure\n      ->\n        priceA as Float\n        priceB as Float\n      <-\n        cheaperPrice as Float: priceA\n\n      if priceB < priceA\n        cheaperPrice: priceB\n\n    compareNames() as pure\n      ->\n        firstName as String\n        lastName as String\n      <-\n        sameIdentity as Boolean: firstName == lastName\n\n  defines program\n\n    SelfOperationsDemo()\n      stdout <- Stdout()\n\n      shopPrice <- 29.99\n      onlinePrice <- 24.99\n      bestPrice <- findCheaper(shopPrice, onlinePrice)\n      stdout.println(`Best price: ${bestPrice}`)\n\n      givenName <- \"John\"\n      familyName <- \"Smith\"\n      identicalName <- compareNames(givenName, familyName)\n      stdout.println(`Same name: ${identicalName}`)","migrationContext":"Java: SonarQube has SelfAssignment and SelfComparison rules but optional. SpotBugs detects SA_FIELD_SELF_ASSIGNMENT. Rust: clippy has eq_op lint for self-comparison (warn by default). Go: go vet detects self-assignment in some cases. Python: no self-comparison detection in standard tools. C++: compiler warnings for some self-comparisons with -Wall. EK9: mandatory compiler error for both self-assignment and self-comparison, covers all comparison operators.","keywords":["E08080","E08081","assignment","clean-code","comparison","constant","copy","error","metric","paste","pointless","quality","self"],"primaryTopics":[],"typicalErrors":[{"error":"E08081","correct":"if priceB < priceA","incorrect":"if priceA < priceA","explanation":"Comparing a variable to itself always produces a constant result (always false for <). This is a copy-paste error. See ek9 -h E08081 for details."},{"error":"E08080","correct":"cheaperPrice: priceB","incorrect":"cheaperPrice: cheaperPrice","explanation":"Assigning a variable to itself is pointless and indicates a copy-paste error. See ek9 -h E08080 for details."}],"companions":[]}
{"id":319,"category":"Code Quality","question":"How does EK9 detect unused parameters?","url":"https://ek9.io/qa/QA0319.html","alternatePhrasings":["What does E08091 unused parameter mean?","Why does EK9 flag unused function arguments?","How does EK9 detect unused closure captures?"],"answer":"EK9 detects parameters and closure captures that are declared but never used in the function body.\n\nUNUSED PARAMETERS (E08091)\nIf a function declares a parameter but never reads it, the parameter is dead code. It increases the function signature complexity without contributing to the result. This usually means the implementation is incomplete or the parameter was left over from a refactoring.\n\nUNUSED CAPTURES (E11018)\nDynamic functions can capture variables from their enclosing scope. If a captured variable is never used inside the dynamic function, the capture is unnecessary. Unused captures waste memory and obscure the function's actual dependencies.\n\nEXEMPT CONTEXTS\nSome parameters are legitimately unused:\n- Abstract methods: parameters define the contract but have no body.\n- Override methods: the overriding method must match the parent signature even if it does not use all parameters.\n- Dispatcher methods: the parameter type drives dispatch but may not be used in the base method.\n- Operators: operator signatures are fixed by convention.\n- Parameterised types: generic type parameters define the shape.\n\nGO AND RUST ANALOGY\nGo requires all variables to be used (unused variable is a compilation error). Rust warns on unused variables with _ prefix convention. EK9 follows Go's philosophy of treating unused code as an error, but is more nuanced with exemptions for abstract, override, and dispatcher contexts.\n\nSee Q311 for the full quality checks catalog. See Q49 for function basics. See Q53 for closure captures.","ek9Example":"defines module qa.codequality.unused\n\n  defines function\n\n    <?-\n      Every parameter is used in the function body.\n      Removing any parameter would change the behaviour.\n    -?>\n    calculateArea() as pure\n      ->\n        width as Float\n        height as Float\n      <-\n        rtn as Float: width * height\n\n    <?-\n      Both parameters contribute to the output.\n    -?>\n    greetPerson() as pure\n      ->\n        title as String\n        surname as String\n      <-\n        greeting as String: `Hello, ${title} ${surname}`\n\n  defines program\n\n    UnusedParametersDemo()\n      stdout <- Stdout()\n\n      roomWidth <- 5.5\n      roomHeight <- 3.2\n      roomArea <- calculateArea(roomWidth, roomHeight)\n      stdout.println(`Room area: ${roomArea} sq metres`)\n\n      personalGreeting <- greetPerson(\"Dr\", \"Watson\")\n      stdout.println(personalGreeting)","migrationContext":"Java: no unused parameter detection in the compiler, relies on IDE inspections or SonarQube. Rust: warns on unused variables, _ prefix suppresses. Go: unused variables are compilation errors, unused function parameters are not checked. Python: no unused parameter detection in standard tools. C++: -Wunused-parameter warns but can be suppressed. Kotlin: IDE inspection only, not a compilation error. EK9: unused parameters and captures are mandatory compiler errors with intelligent exemptions.","keywords":["E08091","E11018","argument","capture","clean-code","code","dead","incomplete","metric","migrate","parameter","quality","refactor","unused"],"primaryTopics":[],"typicalErrors":[{"error":"E50001","correct":"roomArea <- calculateArea(roomWidth, roomHeight)","incorrect":"roomAreaXYZ <- calculateArea(roomWidth, roomHeight)","explanation":"Renaming the variable means later references to 'roomArea' become unresolved, triggering E50001. See ek9 -h E50001 for details."}],"companions":[]}
{"id":320,"category":"Code Quality","question":"Why does EK9 report on readability?","url":"https://ek9.io/qa/QA0320.html","alternatePhrasings":["What is the ARI readability score in EK9?","Does EK9 measure code readability?","How does EK9 check identifier naming quality?"],"answer":"EK9 calculates Automated Readability Index (ARI) scores for identifiers and reports them in quality dashboards. This is informational only, NOT a compilation error.\n\nARI SCORES\nThe ARI score estimates the reading level needed to understand an identifier. Scores range from 1 (simple, short names) to 20+ (very long, complex names). Lower scores mean more readable code. The score considers identifier length, word count (from camelCase splitting), and syllable complexity.\n\nWHY INFORMATIONAL ONLY\nSome domains require long identifiers. Finance has 'annualisedPercentageRate'. Chemistry has 'dihydrogenMonoxide'. Medical software has 'electrocardiogramResult'. Enforcing short names in these domains would reduce clarity, not improve it. So EK9 reports the score but does not reject high-scoring code.\n\nWHERE SCORES APPEAR\nARI scores are visible in the -t6 HTML quality dashboard. Each function and class shows its average identifier readability. Module-level summaries highlight areas where naming could be simplified.\n\nHELPS HUMANS AND AI\nReadable identifiers benefit both human developers and AI assistants. For humans, clearer names reduce cognitive load. For AI, descriptive names mean fewer tokens needed to understand context, leading to more accurate code suggestions.\n\nSee Q311 for the full quality checks catalog. See Q290 for variable naming rules. See Q321 for the quality report dashboard.","ek9Example":"defines module qa.codequality.readability\n\n  defines function\n\n    <?-\n      Short, descriptive names score well on readability.\n      'age' is better than 'currentAgeOfTheUserInYears'.\n    -?>\n    isAdult() as pure\n      -> age as Integer\n      <- rtn as Boolean: false\n\n      legalAge <- 18\n      if age >= legalAge\n        rtn: true\n\n    <?-\n      Domain-specific names are acceptable even when longer.\n      'interestRate' is clear in a financial context.\n    -?>\n    calculateInterest() as pure\n      ->\n        principal as Float\n        interestRate as Float\n        years as Integer\n      <-\n        totalInterest as Float: 0.0\n\n      annualInterest <- principal * interestRate\n      totalInterest: annualInterest * years\n\n  defines program\n\n    ReadabilityScoresDemo()\n      stdout <- Stdout()\n\n      customerAge <- 25\n      adultStatus <- isAdult(customerAge)\n      stdout.println(`Adult: ${adultStatus}`)\n\n      loanAmount <- 10000.0\n      annualRate <- 0.05\n      loanYears <- 3\n      interest <- calculateInterest(loanAmount, annualRate, loanYears)\n      stdout.println(`Interest over ${loanYears} years: ${interest}`)","migrationContext":"Java: no readability scoring in any standard tool. SonarQube measures cognitive complexity but not identifier readability. Rust: no readability scoring. Go: no readability scoring, though short names are a cultural convention. Python: no readability scoring, PEP 8 has length guidelines only. C++: no readability scoring. EK9: ARI readability scoring calculated automatically, shown in HTML dashboard, informational metric for continuous improvement.","keywords":["ARI","clean-code","cognitive","dashboard","identifier","informational","metric","naming","quality","readability","readable","score"],"primaryTopics":[],"typicalErrors":[{"error":"E11064","correct":"legalAge <- 18\n      if age >= legalAge","incorrect":"if age >= 18","explanation":"Using a raw literal 18 in a comparison is a magic literal. Extract it into a named constant that documents the intent. See ek9 -h E11064 for details."}],"companions":[]}
{"id":321,"category":"Code Quality","question":"How do I see the code quality report?","url":"https://ek9.io/qa/QA0321.html","alternatePhrasings":["How do I get an EK9 quality dashboard?","What does the -t6 HTML report show?","How do I view complexity and coverage together?"],"answer":"EK9 generates a comprehensive HTML quality dashboard using the -t6 flag. This combines test results, coverage data, and code quality metrics in a single page.\n\nGENERATING THE REPORT\n  ek9 -t6 myproject.ek9\nThis runs all tests, collects coverage, calculates quality metrics, and generates an HTML dashboard.\n\nFOUR QUALITY DIALS\nThe dashboard header shows four gauges:\n1. Average cyclomatic complexity across all functions.\n2. Maximum cyclomatic complexity (the worst function).\n3. Average cognitive complexity across all functions.\n4. Maximum cognitive complexity (the worst function).\nGreen zone means healthy. Yellow means approaching thresholds. Red means at risk.\n\nMODULE BREAKDOWN\nEach module shows:\n- Number of types, functions, and programs.\n- Module coupling count (how many external modules referenced).\n- Module cohesion score.\n- Per-type LCOM4 and efferent coupling.\n\nFUNCTION-LEVEL DETAIL\nEach function in the source view shows complexity badges:\n- Cyclomatic complexity number.\n- Cognitive complexity number.\n- Nesting depth indicator.\n- Coverage percentage (if tests were run with coverage).\n\nADDING PROFILING (-t6p)\nAppend 'p' to include profiling data:\n  ek9 -t6p myproject.ek9\nThis adds call counts and timing badges to each function in the source view.\n\nCOVERAGE AND QUALITY ON ONE PAGE\nThe -t6 report combines coverage heatmaps with quality metrics. You can see which functions are both poorly tested AND highly complex, the highest risk areas in your codebase.\n\nSee Q206 for test coverage. See Q157 for running tests. See Q312 for complexity metrics. See Q322 for profiling data. See Q207 for test output formats. See Q629 for reading flame graphs.","ek9Example":"defines module qa.codequality.report\n\n  defines function\n\n    <?-\n      Simple functions to demonstrate code that appears in a quality report.\n      Each function would show its own complexity badge.\n    -?>\n    addTax() as pure\n      ->\n        netPrice as Float\n        taxRate as Float\n      <-\n        grossPrice as Float: netPrice * (1.0 + taxRate)\n\n    applyDiscount() as pure\n      ->\n        price as Float\n        discountFraction as Float\n      <-\n        rtn as Float: price * (1.0 - discountFraction)\n\n  defines program\n\n    QualityReportDemo()\n      stdout <- Stdout()\n\n      basePrice <- 100.0\n      standardTaxRate <- 0.20\n      memberDiscount <- 0.10\n\n      withTax <- addTax(basePrice, standardTaxRate)\n      finalPrice <- applyDiscount(withTax, memberDiscount)\n\n      stdout.println(`Base: ${basePrice}`)\n      stdout.println(`With tax: ${withTax}`)\n      stdout.println(`After discount: ${finalPrice}`)\n\n      luxuryThreshold <- 500.0\n      stdout.println(`Threshold: ${luxuryThreshold}`)\n      isLuxury <- finalPrice > luxuryThreshold\n      stdout.println(`Luxury: ${isLuxury}`)","migrationContext":"Java: separate tools generate separate reports. JaCoCo for coverage HTML, SonarQube for quality dashboard, JMH for profiling. No single unified view. Python: coverage.py generates HTML, pylint generates text/JSON, no unified dashboard. Rust: tarpaulin for coverage, no quality dashboard. Go: go tool cover for coverage HTML, no quality dashboard. EK9: single -t6 flag generates unified HTML dashboard with coverage, complexity, cohesion, coupling, and profiling all on one page.","keywords":["HTML","badge","clean-code","complexity","coverage","dashboard","dials","metric","module","quality","report","t6"],"primaryTopics":[],"typicalErrors":[{"error":"E50001","correct":"withTax <- addTax(basePrice, standardTaxRate)","incorrect":"addTax(basePrice, standardTaxRate)","explanation":"Removing the variable declaration means later references to the variable become unresolved, triggering E50001. See ek9 -h E50001 for details."},{"error":"E11064","correct":"finalPrice > luxuryThreshold","incorrect":"finalPrice > 500.0","explanation":"Comparing against a raw literal 500.0 hides the intent behind a 'magic number' and triggers E11064. Extract it to a named constant (luxuryThreshold <- 500.0) so the threshold documents itself. See ek9 -h E11064 for details."}],"companions":[]}
{"id":322,"category":"Code Quality","question":"How do I see how many times code was called and how long it took?","url":"https://ek9.io/qa/QA0322.html","alternatePhrasings":["How do I profile EK9 code?","Does EK9 have a built-in profiler?","How do I identify slow code in EK9?"],"answer":"EK9 has built-in profiling. Append 'p' to any test flag to enable profiling data collection.\n\nENABLING PROFILING\n  ek9 -tp myproject.ek9     Human-readable profiling summary.\n  ek9 -t2p myproject.ek9    JSON profiling data (for AI and CI tools).\n  ek9 -t6p myproject.ek9    HTML dashboard with flame graph.\n\nMETRICS COLLECTED\nFor each function and method:\n- Call count: how many times it was invoked.\n- Total time: wall-clock time including callees.\n- Self time: time spent in this function only, excluding callees.\n- Average time per call.\n- Minimum and maximum call times.\n- Percentiles: p50, p95, p99 for latency distribution.\n\nFLAME GRAPH (-t6p)\nThe HTML dashboard includes an interactive flame graph. Each frame represents a function. Width represents total time.\n- Wide red frames: high self-time. These are the functions doing the most work. Optimise these first.\n- Wide blue frames: orchestrators that call many other functions. High total time but low self-time. Optimising these means restructuring call patterns.\n- Narrow frames: rarely called or fast functions. Usually not worth optimising.\n\nHOT FUNCTION TABLE\nBelow the flame graph, a sorted table lists functions by self-time. The top entries are your optimisation targets. Each entry links to the source view.\n\nJSON OUTPUT FOR CI\nThe -t2p flag produces JSON output suitable for automated analysis:\n- CI pipelines can fail builds if p99 latency exceeds thresholds.\n- AI assistants can read JSON profiling data to suggest optimisations.\n- Trend analysis tools can track performance over time.\n\nSee Q321 for the quality report dashboard. See Q157 for running tests. See Q252 for compiler flags. See Q207 for test output formats. See Q246 for debugging strategies. See Q627 for profiling deep dive. See Q629 for reading flame graphs. See Q630 for identifying hot methods. See Q631 for benchmarking approaches.\nSee Q694 for named arguments pattern. See Q696 for complexity within limits.\nSee Q728 for nesting depth boundary. See Q733 for combined complexity boundary.","ek9Example":"defines module qa.codequality.profiling\n\n  defines function\n\n    <?-\n      Functions that would show different profiling characteristics.\n      A frequently called helper vs a one-time orchestrator.\n    -?>\n    fibonacci() as pure\n      -> position as Integer\n      <- rtn as Integer: 0\n\n      firstBase <- 0\n      secondBase <- 1\n      thresholdForRecursion <- 2\n      if position == firstBase\n        rtn: firstBase\n      else if position == secondBase\n        rtn: secondBase\n      else if position >= thresholdForRecursion\n        previousValue <- fibonacci(position - 1)\n        beforePrevious <- fibonacci(position - 2)\n        rtn: previousValue + beforePrevious\n\n    formatResult() as pure\n      ->\n        label as String\n        number as Integer\n      <-\n        rtn as String: `${label}: ${number}`\n\n  defines program\n\n    ProfilingDemo()\n      stdout <- Stdout()\n\n      targetPosition <- 10\n      result <- fibonacci(targetPosition)\n      output <- formatResult(\"Fibonacci\", result)\n      stdout.println(output)","migrationContext":"Java: JMH for microbenchmarks, async-profiler or JFR for profiling (all external tools). Python: cProfile, line_profiler, py-spy (all external). Rust: perf, flamegraph crate, criterion for benchmarks (all external). Go: go tool pprof built-in with -cpuprofile flag. JavaScript: Chrome DevTools profiler. EK9: append 'p' to any test flag for built-in profiling, flame graph in HTML dashboard, JSON output for AI/CI integration.","keywords":["bottleneck","call","clean-code","count","flame","graph","metric","p95","p99","performance","profile","profiling","quality","self","time","total"],"primaryTopics":["profiling","performance profiling"],"typicalErrors":[{"error":"E50001","correct":"firstBase <- 0\n      secondBase <- 1\n      thresholdForRecursion <- 2","incorrect":"if position == 0\n        rtn: 0\n      else if position == 1\n        rtn: 1","explanation":"Using raw literals 0, 1, 2 in comparisons is a magic literal error. Extract them into named constants like firstBase, secondBase, and thresholdForRecursion. See ek9 -h E50001 for details."},{"error":"E50001","correct":"result <- fibonacci(targetPosition)","incorrect":"fibonacci(targetPosition)","explanation":"Removing the variable declaration means later references to the variable become unresolved, triggering E50001. See ek9 -h E50001 for details."}],"companions":[]}
{"id":323,"category":"Code Quality","question":"Can AI help me improve code quality?","url":"https://ek9.io/qa/QA0323.html","alternatePhrasings":["How do I use AI with EK9 code quality?","What EK9 commands help AI understand my code?","How does EK9 support AI-assisted development?"],"answer":"EK9 is designed from the start for AI-assisted development. Multiple commands produce machine-readable output that AI tools can consume directly.\n\nSTEP 1: RICH ERROR MESSAGES (-E3)\n  ek9 -c -E3 mycode.ek9\nThe -E3 flag produces detailed error explanations with diagnosis, rationale, and fix examples. AI assistants can read these to understand exactly what went wrong and generate correct fixes.\n\nSTEP 2: JSON PROFILING AND COVERAGE (-t2p)\n  ek9 -t2p mycode.ek9\nJSON output with profiling data, test results, and coverage. AI tools can parse this to identify undertested code, performance hotspots, and quality trends.\n\nSTEP 3: REFERENCE DATA (forAI.json)\nThe forAI.json file contains the complete EK9 knowledge base in machine-readable format. AI assistants can load this as context to understand EK9 syntax, operators, and patterns without guessing.\n\nSTEP 4: KNOWLEDGE SEARCH (-q)\n  ek9 -q \"how to handle errors\"\nSearch the Q&A knowledge base from the command line. AI tools can use this to find correct patterns for specific problems instead of hallucinating incorrect syntax.\n\nSTEP 5: TRAINING DATA DUMP (-Q)\n  ek9 -Q\nDumps the entire Q&A knowledge base in JSONL training format. This can fine-tune local AI models on EK9 patterns.\n\nTHE AI QUALITY WORKFLOW\n1. Write code (or have AI generate it).\n2. Compile with -E3 for rich feedback.\n3. Fix errors using Q&A search (-q) for correct patterns.\n4. Run tests with -t2p for JSON quality data.\n5. Feed quality data back to AI for continuous improvement.\n\nEK9 DESIGNED FOR AI\nThe compiler error messages are specifically designed as AI control signals. Each error produces a cascade that guides AI toward correct patterns. When the compiler is silent, the code has converged across all quality dimensions.\n\nSee Q281 for verifying AI-generated code. See Q252 for all compiler flags. See Q311 for quality checks catalog. See Q321 for the quality report.\n\nSee Q340 for AI-friendly transaction patterns.","ek9Example":"defines module qa.codequality.ai\n\n  defines function\n\n    <?-\n      A function that demonstrates clean, AI-friendly code.\n      Clear names, simple structure, obvious purpose.\n    -?>\n    categoriseResponse() as pure\n      -> statusCode as Integer\n      <- category as String: \"unknown\"\n\n      successLower <- 200\n      successUpper <- 299\n      clientErrorLower <- 400\n      clientErrorUpper <- 499\n      serverErrorLower <- 500\n      serverErrorUpper <- 599\n\n      if statusCode >= successLower and statusCode <= successUpper\n        category: \"success\"\n      else if statusCode >= clientErrorLower and statusCode <= clientErrorUpper\n        category: \"client error\"\n      else if statusCode >= serverErrorLower and statusCode <= serverErrorUpper\n        category: \"server error\"\n\n  defines program\n\n    AiCodeQualityDemo()\n      stdout <- Stdout()\n\n      okStatus <- 200\n      notFoundStatus <- 404\n\n      okCategory <- categoriseResponse(okStatus)\n      errorCategory <- categoriseResponse(notFoundStatus)\n\n      stdout.println(`Status ${okStatus}: ${okCategory}`)\n      stdout.println(`Status ${notFoundStatus}: ${errorCategory}`)","migrationContext":"Java: SonarQube has API output but requires separate server. IDE plugins provide some AI integration. No unified CLI workflow. Python: pylint has JSON output, coverage has JSON, but no unified AI workflow. Rust: clippy has JSON output with --message-format=json. No knowledge base. Go: go vet has JSON output. No integrated knowledge base. EK9: purpose-built for AI with -E3 rich errors, -t2p JSON quality data, forAI.json reference, -q knowledge search, -Q training dump, all from a single CLI.","keywords":["E3","ai","assistant","clean-code","forAI","json","knowledge","llm","machine","metric","quality","readable","training","workflow"],"primaryTopics":[],"typicalErrors":[{"error":"E50001","correct":"okCategory <- categoriseResponse(okStatus)","incorrect":"okCategoryXYZ <- categoriseResponse(okStatus)","explanation":"Renaming the variable means later references to 'okCategory' become unresolved, triggering E50001. See ek9 -h E50001 for details."},{"error":"E50001","correct":"errorCategory <- categoriseResponse(notFoundStatus)","incorrect":"errorCategoryXYZ <- categoriseResponse(notFoundStatus)","explanation":"Renaming the variable means later references to 'errorCategory' become unresolved, triggering E50001. See ek9 -h E50001 for details."}],"companions":[]}
{"id":324,"category":"Dependency Injection","question":"What is the EK9 equivalent of Spring @Autowired?","url":"https://ek9.io/qa/QA0324.html","alternatePhrasings":["How do I inject dependencies in EK9 like Spring @Autowired?","What replaces @Autowired in EK9?","How do I wire dependencies without @Autowired?"],"answer":"EK9 replaces Spring's @Autowired with the injection suffix '!' on field declarations. Any field ending with '!' is an injection point that the compiler validates at compile time.\n\nSPRING @AUTOWIRED VS EK9 '!'\nIn Spring, @Autowired marks a field for runtime injection:\n  @Autowired private UserService userService;\nIn EK9, the '!' suffix marks a field for compile-time validated injection:\n  userService as UserService!\nThe '!' suffix is shorter, more visible, and validated before any code runs.\n\nCOMPILE-TIME GUARANTEE\nSpring discovers missing @Autowired bindings at runtime (NoSuchBeanDefinitionException, sometimes minutes into startup). EK9 discovers missing injection bindings during compilation. If your program compiles, every injection point is satisfied.\n\nFIELD INJECTION ONLY\nEK9 supports field injection (the '!' suffix). There is no constructor injection or method injection — fields with '!' are populated during the application's prepare phase before the program body executes.\n\nWHY NO CONSTRUCTOR INJECTION\nSpring advocates constructor injection for immutability and testability. EK9 achieves both through different mechanisms: compile-time validation ensures completeness, and the singleton lifecycle ensures stability. Constructor injection adds complexity without additional safety in EK9's model.\n\nSee Q227 for compile-time DI validation. See Q228 for registration ordering. See Q231 for program-application linking. See Q331 for testing injected components.\n\nSee Q667 for abstract injection requirement. See Q669 for injection reassignment.","ek9Example":"defines module qa.di.spring.autowired\n\n  defines component\n\n    UserRepository as abstract\n      findUser() as abstract\n        -> userId as String\n        <- userName as String?\n\n      default operator ?\n\n    InMemoryUserRepository is UserRepository\n      override findUser()\n        -> userId as String\n        <- userName as String: \"User-\" + userId\n\n      default operator ?\n\n    <?-\n      UserService uses '!' to mark injection point.\n      This is the EK9 equivalent of @Autowired.\n    -?>\n    UserService as abstract\n      getUser() as abstract\n        -> userId as String\n        <- greeting as String?\n\n      default operator ?\n\n    DefaultUserService is UserService\n      repo as UserRepository!\n\n      override getUser()\n        -> userId as String\n        <- greeting <- String()\n        userName <- repo.findUser(userId)\n        greeting: \"Hello, \" + userName\n\n      default operator ?\n\n  defines application\n\n    AutowiredEquivalentApp\n      register InMemoryUserRepository() as UserRepository\n      register DefaultUserService() as UserService\n\n  defines program\n\n    AutowiredDemo() with application of AutowiredEquivalentApp\n      stdout <- Stdout()\n\n      // === '!' SUFFIX IS THE EK9 EQUIVALENT OF @Autowired ===\n\n      service as UserService!\n\n      result <- service.getUser(\"42\")\n      stdout.println(result)\n\n      stdout.println(\"Injection validated at compile time, not runtime\")","migrationContext":"Java Spring: @Autowired on fields, constructors, or setters. Runtime injection via reflection. Missing beans throw NoSuchBeanDefinitionException at startup. Constructor injection recommended for immutability. Guice: @Inject annotation, similar to @Autowired. .NET: constructor injection via IServiceProvider. Python: no built-in DI. Go: no built-in DI. EK9: '!' suffix on field declarations, compile-time validation, zero runtime injection failures.","keywords":["autowired","binding","dependency","exclamation","field","inject","migration","spring","suffix","wiring"],"primaryTopics":[],"typicalErrors":[{"error":"E08150","correct":"repo as UserRepository!","incorrect":"repo as InMemoryUserRepository!","explanation":"Injection fields must declare abstract component types. The '!' suffix marks a field for injection, but the type must be the abstract contract, not the concrete implementation. See ek9 -h E08150 for details."},{"error":"E08210","correct":"register InMemoryUserRepository() as UserRepository\n      register DefaultUserService() as UserService","incorrect":"register DefaultUserService() as UserService","explanation":"Removing the UserRepository registration leaves DefaultUserService's injection field 'repo as UserRepository!' unsatisfied. Every injection field must have a matching registration. See ek9 -h E08210 for details."}],"companions":[]}
{"id":325,"category":"Dependency Injection","question":"What is the EK9 equivalent of Spring @Component and @Service?","url":"https://ek9.io/qa/QA0325.html","alternatePhrasings":["How do I define injectable components in EK9?","What replaces @Component and @Service annotations in EK9?","How does EK9 component scanning work?"],"answer":"EK9 replaces Spring's @Component and @Service with the 'defines component' construct. Components in EK9 are types that can be registered in applications and injected into programs.\n\nSPRING ANNOTATIONS VS EK9 CONSTRUCTS\nSpring uses @Component/@Service annotations on classes that are auto-scanned:\n  @Service public class OrderService { ... }\nEK9 uses the 'defines component' section to declare injectable types:\n  defines component\n    OrderService as abstract ...\n    DefaultOrderService is OrderService ...\n\nNO AUTO-SCANNING\nSpring scans the classpath for annotated classes. EK9 requires explicit registration in the application definition. This is intentional: explicit registration is auditable, predictable, and compile-time verified.\n\nABSTRACT + CONCRETE PATTERN\nEK9 components follow the abstract/concrete pattern: define an abstract component (the contract), then implement concrete components. Register the concrete as the abstract in the application. Programs inject the abstract type.\n\nWHY NO @SERVICE/@REPOSITORY DISTINCTION\nSpring has @Component, @Service, @Repository, and @Controller. These are functionally identical (all are component stereotypes). EK9 has one construct: component. Semantic distinctions belong in naming, not annotations.\n\nSee Q111 for component basics. See Q227 for compile-time validation. See Q324 for @Autowired equivalent. See Q326 for @Bean/@Configuration equivalent.","ek9Example":"defines module qa.di.spring.component\n\n  defines component\n\n    <?-\n      Abstract component is the contract (like Spring interface).\n    -?>\n    NotificationService as abstract\n      notify() as abstract\n        -> message as String\n        <- confirmation as String?\n\n      default operator ?\n\n    <?-\n      Concrete component is the implementation (like @Service class).\n    -?>\n    EmailNotifier is NotificationService\n      override notify()\n        -> message as String\n        <- confirmation as String: \"Email sent: \" + message\n\n      default operator ?\n\n    AuditService as abstract\n      record() as abstract\n        -> action as String\n        <- entry as String?\n\n      default operator ?\n\n    SimpleAuditService is AuditService\n      notifier as NotificationService!\n\n      override record()\n        -> action as String\n        <- entry <- String()\n        entry: notifier.notify(\"Audit: \" + action)\n\n      default operator ?\n\n  defines application\n\n    ComponentApp\n      register EmailNotifier() as NotificationService\n      register SimpleAuditService() as AuditService\n\n  defines program\n\n    ComponentServiceDemo() with application of ComponentApp\n      stdout <- Stdout()\n\n      // === COMPONENTS REPLACE @Component AND @Service ===\n\n      audit as AuditService!\n\n      result <- audit.record(\"user.login\")\n      stdout.println(result)\n\n      stdout.println(\"Components defined in 'defines component' section\")","migrationContext":"Java Spring: @Component, @Service, @Repository, @Controller stereotypes with classpath scanning. Guice: @Provides methods in Module classes. .NET: services.AddTransient/AddScoped/AddSingleton in ConfigureServices. Python: no language-level component declaration. Go: no language-level DI. EK9: 'defines component' section with abstract/concrete pattern, explicit registration in application, no classpath scanning.","keywords":["annotation","component","construct","define","dependency","inject","injectable","migration","register","service","spring","stereotype"],"primaryTopics":[],"typicalErrors":[{"error":"E08150","correct":"notifier as NotificationService!","incorrect":"notifier as EmailNotifier!","explanation":"Injection fields must declare the abstract component type. The concrete EmailNotifier is registered against the abstract NotificationService in the application, not injected directly. See ek9 -h E08150 for details."},{"error":"E08200","correct":"register EmailNotifier() as NotificationService\n      register SimpleAuditService() as AuditService","incorrect":"register SimpleAuditService() as AuditService\n      register EmailNotifier() as NotificationService","explanation":"SimpleAuditService injects NotificationService, so NotificationService must be registered before SimpleAuditService. Reversing the order causes a missing dependency at that point in the registration sequence. See ek9 -h E08200 for details."},{"error":"E50001","correct":"result <- audit.record(\"user.login\")","incorrect":"resultXYZ <- audit.record(\"user.login\")","explanation":"Renaming the variable means later references to 'result' become unresolved, triggering E50001. See ek9 -h E50001 for details."}],"companions":[]}
{"id":326,"category":"Dependency Injection","question":"What is the EK9 equivalent of Spring @Bean and @Configuration?","url":"https://ek9.io/qa/QA0326.html","alternatePhrasings":["How do I configure bean creation in EK9?","What replaces @Configuration classes in EK9?","How do I register third-party types for injection in EK9?"],"answer":"EK9 replaces Spring's @Configuration classes and @Bean methods with the 'defines application' construct. The application definition is where you register concrete implementations against abstract types.\n\nSPRING @BEAN VS EK9 APPLICATION REGISTRATION\nSpring uses @Configuration with @Bean methods:\n  @Configuration\n  class AppConfig {\n    @Bean DataSource dataSource() { return new HikariDataSource(); }\n  }\nEK9 uses explicit registration in the application:\n  defines application\n    MyApp\n      register HikariDataSource() as DataSource\n\nEXPLICIT CONSTRUCTION\nIn Spring, @Bean methods can contain arbitrary construction logic. In EK9, the register statement calls the component's constructor directly. Complex construction logic belongs in the component's constructor, not in configuration.\n\nORDERING IS EXPLICIT\nSpring resolves bean creation order automatically (and sometimes gets it wrong). EK9 requires registration in dependency order: dependencies before dependents. The compiler validates this ordering at compile time (see Q228).\n\nNO CONDITIONAL BEANS\nSpring has @Conditional, @Profile, @ConditionalOnProperty for conditional bean creation. EK9 does not support conditional registration. If you need different configurations, define different applications. This eliminates an entire class of 'hidden missing bean' bugs.\n\nSee Q227 for compile-time validation. See Q228 for registration ordering. See Q324 for @Autowired equivalent. See Q325 for @Component equivalent.","ek9Example":"defines module qa.di.spring.bean.config\n\n  defines component\n\n    CacheService as abstract\n      lookup() as abstract\n        -> key as String\n        <- hit as String?\n\n      default operator ?\n\n    InMemoryCache is CacheService\n      override lookup()\n        -> key as String\n        <- hit as String: \"cached:\" + key\n\n      default operator ?\n\n    DataService as abstract\n      fetch() as abstract\n        -> query as String\n        <- result as String?\n\n      default operator ?\n\n    CachedDataService is DataService\n      cache as CacheService!\n\n      override fetch()\n        -> query as String\n        <- result <- String()\n        result: cache.lookup(query)\n\n      default operator ?\n\n  defines application\n\n    <?-\n      This application definition replaces @Configuration class.\n      Each register statement replaces a @Bean method.\n    -?>\n    BeanConfigApp\n      register InMemoryCache() as CacheService\n      register CachedDataService() as DataService\n\n  defines program\n\n    BeanConfigDemo() with application of BeanConfigApp\n      stdout <- Stdout()\n\n      // === APPLICATION DEFINITION REPLACES @Configuration ===\n\n      dataService as DataService!\n\n      result <- dataService.fetch(\"users\")\n      stdout.println(result)\n\n      stdout.println(\"Application construct replaces @Configuration + @Bean\")","migrationContext":"Java Spring: @Configuration + @Bean methods, auto-wiring, @Conditional/@Profile for conditional beans, FactoryBean for complex creation. Guice: Module.configure() with bind() statements. .NET: ConfigureServices with AddTransient/AddScoped/AddSingleton. Python: manual construction or DI frameworks. Go: wire for compile-time DI, manual construction. EK9: 'defines application' with explicit 'register' statements, dependency ordering enforced by compiler, no conditional registration.","keywords":["application","bean","conditional","configuration","create","dependency","explicit","factory","inject","migration","register","spring"],"primaryTopics":[],"typicalErrors":[{"error":"E08200","correct":"register InMemoryCache() as CacheService\n      register CachedDataService() as DataService","incorrect":"register CachedDataService() as DataService\n      register InMemoryCache() as CacheService","explanation":"CachedDataService injects CacheService, so CacheService must be registered first. Unlike Spring which resolves order automatically, EK9 requires explicit dependency-first ordering. See ek9 -h E08200 for details."},{"error":"E08150","correct":"cache as CacheService!","incorrect":"cache as InMemoryCache!","explanation":"Injection fields must use the abstract type. The application maps concrete to abstract via 'register X() as Y', and fields inject the abstract Y. See ek9 -h E08150 for details."}],"companions":[]}
{"id":327,"category":"Dependency Injection","question":"How does EK9 DI compare to Spring Boot auto-configuration?","url":"https://ek9.io/qa/QA0327.html","alternatePhrasings":["Does EK9 have auto-configuration like Spring Boot?","How does EK9 handle what Spring Boot starters provide?","What is different between Spring Boot and EK9 dependency injection?"],"answer":"Spring Boot's auto-configuration automatically creates beans based on classpath contents and properties. EK9 deliberately rejects auto-configuration in favor of explicit registration.\n\nSPRING BOOT AUTO-CONFIGURATION\nSpring Boot scans for @AutoConfiguration classes, evaluates @Conditional annotations, and creates beans based on what's on the classpath. A single dependency like spring-boot-starter-web auto-configures an embedded Tomcat, Jackson, DispatcherServlet, and dozens of other beans.\n\nWHY EK9 REJECTS AUTO-CONFIGURATION\n1. INVISIBLE BEHAVIOUR: Auto-configured beans appear from nowhere. Developers cannot tell which beans exist without running the application and inspecting the context.\n2. FRAGILE ORDERING: Auto-configuration order depends on classpath scanning, which can change between builds. This causes intermittent failures.\n3. CONDITIONAL COMPLEXITY: @ConditionalOnClass, @ConditionalOnMissingBean, @ConditionalOnProperty create a combinatorial explosion of possible configurations.\n4. DEBUGGING NIGHTMARE: When auto-configuration goes wrong, developers must trace through AbstractAutowireCapableBeanFactory and dozens of post-processors.\n\nEK9'S EXPLICIT APPROACH\nIn EK9, every component registration is visible in the application definition. There is no magic. If you can read the 'defines application' section, you know exactly what components exist.\n\nBENEFITS OF EXPLICITNESS\n1. AUDITABLE: Security teams can review exactly what's registered\n2. PREDICTABLE: Same registration, same behavior, every time\n3. COMPILE-TIME VERIFIED: Compiler checks completeness and ordering\n4. AI-FRIENDLY: AI can read and generate correct registrations\n\nSee Q227 for compile-time validation. See Q326 for @Bean equivalent. See Q332 for runtime overhead comparison.","ek9Example":"defines module qa.di.spring.boot.comparison\n\n  defines component\n\n    Logger as abstract\n      log() as abstract\n        -> message as String\n        <- output as String?\n\n      default operator ?\n\n    ConsoleLogger is Logger\n      override log()\n        -> message as String\n        <- output as String: \"[LOG] \" + message\n\n      default operator ?\n\n    Repository as abstract\n      find() as abstract\n        -> identifier as String\n        <- record as String?\n\n      default operator ?\n\n    SimpleRepository is Repository\n      logger as Logger!\n\n      override find()\n        -> identifier as String\n        <- record <- String()\n        record: logger.log(\"Found: \" + identifier)\n\n      default operator ?\n\n    AppController as abstract\n      handle() as abstract\n        -> request as String\n        <- response as String?\n\n      default operator ?\n\n    MainController is AppController\n      repo as Repository!\n\n      override handle()\n        -> request as String\n        <- response <- String()\n        response: repo.find(request)\n\n      default operator ?\n\n  defines application\n\n    <?-\n      Every registration is explicit and visible.\n      No auto-configuration, no classpath magic.\n    -?>\n    ExplicitApp\n      register ConsoleLogger() as Logger\n      register SimpleRepository() as Repository\n      register MainController() as AppController\n\n  defines program\n\n    SpringBootComparisonDemo() with application of ExplicitApp\n      stdout <- Stdout()\n\n      // === EXPLICIT REGISTRATION VS AUTO-CONFIGURATION ===\n\n      controller as AppController!\n\n      result <- controller.handle(\"user-123\")\n      stdout.println(result)\n\n      stdout.println(\"Every component explicitly registered and verified\")","migrationContext":"Java Spring Boot: @SpringBootApplication triggers auto-configuration, starters provide pre-configured beans, @Conditional for conditional creation, spring.factories/META-INF for discovery. Quarkus: build-time configuration similar to EK9 philosophy. Micronaut: compile-time DI, closer to EK9 approach. .NET: Host.CreateDefaultBuilder with auto-configuration. EK9: explicit registration only, no auto-configuration, no classpath scanning, compile-time validation.","keywords":["auto","classpath","configuration","dependency","explicit","inject","invisible","migration","registration","scanning","spring boot","starter"],"primaryTopics":[],"typicalErrors":[{"error":"E08200","correct":"register ConsoleLogger() as Logger\n      register SimpleRepository() as Repository\n      register MainController() as AppController","incorrect":"register MainController() as AppController\n      register SimpleRepository() as Repository\n      register ConsoleLogger() as Logger","explanation":"Unlike Spring Boot auto-configuration which resolves order automatically, EK9 requires dependencies registered before dependents. MainController depends on Repository which depends on Logger, so Logger must come first. See ek9 -h E08200 for details."},{"error":"E50001","correct":"result <- controller.handle(\"user-123\")","incorrect":"resultXYZ <- controller.handle(\"user-123\")","explanation":"Renaming the variable means later references to 'result' become unresolved, triggering E50001. See ek9 -h E50001 for details."}],"companions":[]}
{"id":328,"category":"Dependency Injection","question":"How does EK9 DI compare to Quarkus build-time injection?","url":"https://ek9.io/qa/QA0328.html","alternatePhrasings":["Is EK9 DI similar to Quarkus compile-time DI?","How does Quarkus ArC compare to EK9 dependency injection?","What are the differences between Quarkus and EK9 DI?"],"answer":"Quarkus and EK9 share the philosophy of build-time DI validation, but EK9 goes further with complete compile-time guarantees and simpler semantics.\n\nQUARKUS ARC (BUILD-TIME CDI)\nQuarkus uses ArC, a build-time CDI implementation that resolves beans during the build rather than at runtime. This eliminates reflection-based injection and improves startup time.\n\nSIMILARITIES WITH EK9\n1. Both validate injection during the build, not at runtime\n2. Both reject circular dependencies before execution\n3. Both aim for zero-overhead injection at runtime\n4. Both improve startup time over Spring\n\nKEY DIFFERENCES\n1. SCOPE: Quarkus supports @ApplicationScoped, @RequestScoped, @Dependent. EK9 has singleton only. Simpler model, fewer scope-mismatch bugs.\n2. CDI COMPATIBILITY: Quarkus implements CDI spec (partially). EK9 has its own injection model with no legacy compatibility burden.\n3. CONDITIONAL BEANS: Quarkus supports @IfBuildProfile and build-time conditions. EK9 has no conditional registration.\n4. INTERCEPTION: Quarkus supports @Interceptor and @AroundInvoke. EK9 uses aspects (a different but equally powerful mechanism).\n5. COMPLETENESS: Quarkus validates what it can at build time but some CDI features still require runtime checks. EK9 validates everything at compile time.\n\nWHY EK9 IS SIMPLER\nQuarkus carries CDI legacy: qualifiers, alternatives, stereotypes, decorators, events. EK9 has: components, applications, injection fields ('!'). The entire DI model fits in one page.\n\nSee Q227 for compile-time validation. See Q234 for lifecycle. See Q327 for Spring Boot comparison.","ek9Example":"defines module qa.di.quarkus.comparison\n\n  defines component\n\n    ConfigProvider as abstract\n      getValue() as abstract\n        -> key as String\n        <- setting as String?\n\n      default operator ?\n\n    DefaultConfig is ConfigProvider\n      override getValue()\n        -> key as String\n        <- setting as String: \"config:\" + key\n\n      default operator ?\n\n    HealthChecker as abstract\n      check() as abstract\n        <- status as String?\n\n      default operator ?\n\n    AppHealthChecker is HealthChecker\n      config as ConfigProvider!\n\n      override check()\n        <- status <- String()\n        appName <- config.getValue(\"app.name\")\n        status: \"Healthy: \" + appName\n\n      default operator ?\n\n  defines application\n\n    BuildTimeApp\n      register DefaultConfig() as ConfigProvider\n      register AppHealthChecker() as HealthChecker\n\n  defines program\n\n    QuarkusComparisonDemo() with application of BuildTimeApp\n      stdout <- Stdout()\n\n      // === COMPILE-TIME DI LIKE QUARKUS BUT SIMPLER ===\n\n      health as HealthChecker!\n\n      result <- health.check()\n      stdout.println(result)\n\n      stdout.println(\"Build-time DI with simpler model than Quarkus CDI\")","migrationContext":"Quarkus ArC: build-time CDI, @ApplicationScoped/@RequestScoped/@Dependent scopes, @Inject annotation, partial CDI spec compliance, build-time bean discovery. Micronaut: compile-time DI via annotation processing, similar to Quarkus but non-CDI. Spring: runtime DI via reflection. EK9: compile-time DI with '!' suffix, singleton scope only, explicit application registration, complete compile-time validation with zero runtime DI failures.","keywords":["arc","build","cdi","compile","dependency","inject","migration","overhead","quarkus","scope","startup","zero"],"primaryTopics":[],"typicalErrors":[{"error":"E08150","correct":"config as ConfigProvider!","incorrect":"config as DefaultConfig!","explanation":"Unlike Quarkus CDI which can inject concrete types, EK9 requires injection fields to use abstract component types. This enforces the contract-based design pattern. See ek9 -h E08150 for details."},{"error":"E08200","correct":"register DefaultConfig() as ConfigProvider\n      register AppHealthChecker() as HealthChecker","incorrect":"register AppHealthChecker() as HealthChecker\n      register DefaultConfig() as ConfigProvider","explanation":"AppHealthChecker injects ConfigProvider, so ConfigProvider must be registered first. EK9 validates registration order at compile time, unlike Quarkus which resolves at build time with more flexible ordering. See ek9 -h E08200 for details."}],"companions":[]}
{"id":329,"category":"Dependency Injection","question":"How does EK9 DI compare to Google Guice modules?","url":"https://ek9.io/qa/QA0329.html","alternatePhrasings":["What replaces Guice modules and bindings in EK9?","How is EK9 injection different from Google Guice?","Does EK9 have something like Guice's AbstractModule?"],"answer":"Google Guice uses Module classes with bind() statements to configure injection. EK9's 'defines application' with 'register' statements serves the same purpose but with compile-time validation.\n\nGUICE MODULES VS EK9 APPLICATIONS\nGuice:\n  class AppModule extends AbstractModule {\n    @Override void configure() {\n      bind(Service.class).to(ServiceImpl.class);\n    }\n  }\nEK9:\n  defines application\n    MyApp\n      register ServiceImpl() as Service\n\nKEY DIFFERENCES\n1. VALIDATION TIMING: Guice validates at Injector creation (runtime). EK9 validates at compilation.\n2. JUST-IN-TIME BINDINGS: Guice can create instances without explicit bindings (JIT). EK9 requires explicit registration for every injection point.\n3. LINKED BINDINGS: Guice chains bind(A).to(B).to(C). EK9 registers concrete against abstract directly.\n4. SCOPES: Guice has @Singleton, @RequestScoped, custom scopes. EK9 has singleton only.\n5. PROVIDERS: Guice has Provider<T> for lazy/deferred creation. EK9 creates all components eagerly in prepare phase.\n\nWHY NO JIT BINDINGS\nGuice's JIT bindings mean a class with @Inject constructor can be injected without any bind() call. This is convenient but dangerous: it hides dependencies and makes the object graph unpredictable. EK9 requires every injection point to have an explicit registration.\n\nINSTALLING MODULES\nGuice composes modules: install(new DatabaseModule()). EK9 does not compose applications — each application is self-contained. If you need shared registrations, define shared components and register them in each application.\n\nSee Q227 for compile-time validation. See Q228 for registration ordering. See Q324 for injection syntax. See Q330 for scope comparison.","ek9Example":"defines module qa.di.guice.comparison\n\n  defines component\n\n    PaymentGateway as abstract\n      charge() as abstract\n        -> amount as String\n        <- receipt as String?\n\n      default operator ?\n\n    StripeGateway is PaymentGateway\n      override charge()\n        -> amount as String\n        <- receipt as String: \"Stripe charged: \" + amount\n\n      default operator ?\n\n    OrderProcessor as abstract\n      process() as abstract\n        -> orderId as String\n        <- confirmation as String?\n\n      default operator ?\n\n    DefaultOrderProcessor is OrderProcessor\n      gateway as PaymentGateway!\n\n      override process()\n        -> orderId as String\n        <- confirmation <- String()\n        confirmation: gateway.charge(\"100.00 for \" + orderId)\n\n      default operator ?\n\n  defines application\n\n    <?-\n      This replaces Guice's AbstractModule.configure().\n      register replaces bind().to().\n    -?>\n    GuiceEquivalentApp\n      register StripeGateway() as PaymentGateway\n      register DefaultOrderProcessor() as OrderProcessor\n\n  defines program\n\n    GuiceComparisonDemo() with application of GuiceEquivalentApp\n      stdout <- Stdout()\n\n      // === EXPLICIT REGISTRATION REPLACES bind().to() ===\n\n      processor as OrderProcessor!\n\n      result <- processor.process(\"ORD-001\")\n      stdout.println(result)\n\n      stdout.println(\"Application registration replaces Guice modules\")","migrationContext":"Google Guice: AbstractModule.configure() with bind().to() statements, @Provides methods, just-in-time bindings, Injector creation validates at runtime, Multibinder for collections, @Singleton scope. Dagger: compile-time DI for Android, @Component/@Module annotations. EK9: 'defines application' with 'register' statements, compile-time validation, no JIT bindings, explicit registration required, singleton scope only.","keywords":["bind","dagger","dependency","explicit","google","guice","inject","injector","jit","migration","module","provider"],"primaryTopics":[],"typicalErrors":[{"error":"E08150","correct":"gateway as PaymentGateway!","incorrect":"gateway as StripeGateway!","explanation":"Unlike Guice which supports JIT bindings to concrete types, EK9 requires injection fields to use abstract component types. Explicit registration maps concrete to abstract. See ek9 -h E08150 for details."},{"error":"E08210","correct":"register StripeGateway() as PaymentGateway\n      register DefaultOrderProcessor() as OrderProcessor","incorrect":"register DefaultOrderProcessor() as OrderProcessor","explanation":"Removing the PaymentGateway registration leaves DefaultOrderProcessor's injection field unsatisfied. Unlike Guice JIT bindings, EK9 requires every injection point to have an explicit registration. See ek9 -h E08210 for details."}],"companions":[]}
{"id":330,"category":"Dependency Injection","question":"Does EK9 support DI scopes like singleton, prototype, and request?","url":"https://ek9.io/qa/QA0330.html","alternatePhrasings":["Can I create request-scoped or prototype-scoped beans in EK9?","Why does EK9 only have singleton scope?","How do I handle different component lifetimes in EK9?"],"answer":"EK9 has exactly one DI scope: singleton for the program lifetime. There are no prototype, request, session, or application scopes. This is a deliberate design decision.\n\nWHY SINGLETON ONLY\n1. SCOPE MISMATCH BUGS: In Spring, injecting a request-scoped bean into a singleton silently breaks. This is one of the most common and hardest-to-diagnose DI bugs. EK9 eliminates this entire bug category.\n2. SIMPLICITY: One scope means one mental model. No scoped proxies, no scope resolution strategies, no lifecycle callbacks per scope.\n3. PREDICTABILITY: Every injection point references the same instance throughout program execution.\n\nWHAT ABOUT PROTOTYPE SCOPE?\nSpring's prototype scope creates a new instance per injection point. In EK9, if you need fresh instances, create them explicitly in your code. This makes object creation visible rather than hidden behind framework magic.\n\nWHAT ABOUT REQUEST SCOPE?\nFor web services, EK9 creates request-specific state explicitly within the service handler. The service component is a singleton, but it creates per-request state using local variables and function calls.\n\nHOW TO HANDLE DIFFERENT LIFETIMES\nUse explicit construction for short-lived objects:\n  service.handle(request)\n    localState <- RequestState(request)\n    processWithState(localState)\nThe component is a singleton; the request state is a local variable. No scope annotation needed.\n\nSee Q234 for component lifecycle. See Q117 for singleton pattern. See Q227 for compile-time validation. See Q329 for Guice scope comparison.","ek9Example":"defines module qa.di.scopes\n\n  defines component\n\n    RequestHandler as abstract\n      handle() as abstract\n        -> path as String\n        <- response as String?\n\n      default operator ?\n\n    <?-\n      The handler is a singleton component.\n      Per-request state is created as local variables.\n    -?>\n    WebHandler is RequestHandler\n\n      override handle()\n        -> path as String\n        <- response <- String()\n\n        // Per-request state created locally, not scope-managed\n        requestId <- \"REQ-\" + path\n        timestamp <- \"2026-02-26\"\n\n        response: `${requestId} at ${timestamp}`\n\n      default operator ?\n\n  defines application\n\n    SingletonApp\n      register WebHandler() as RequestHandler\n\n  defines program\n\n    ScopeDemo() with application of SingletonApp\n      stdout <- Stdout()\n\n      // === SINGLETON COMPONENT, LOCAL STATE PER REQUEST ===\n\n      handler as RequestHandler!\n\n      // Simulate multiple requests to same singleton handler\n      response1 <- handler.handle(\"/users\")\n      stdout.println(response1)\n\n      response2 <- handler.handle(\"/orders\")\n      stdout.println(response2)\n\n      stdout.println(\"Singleton component handles all requests\")\n      stdout.println(\"Per-request state uses local variables\")","migrationContext":"Java Spring: singleton (default), prototype, request, session, application, websocket scopes. Scope mismatch is a common bug source. Guice: unscoped, @Singleton, @RequestScoped, custom scopes via Scope interface. .NET: transient, scoped, singleton. Quarkus: @ApplicationScoped, @RequestScoped, @Dependent. EK9: singleton only, explicit construction for short-lived objects, no scope mismatch bugs possible, local variables for per-request state.","keywords":["dependency","inject","lifetime","migrate","mismatch","prototype","proxy","request","scope","session","simplicity","singleton","transient"],"primaryTopics":[],"typicalErrors":[{"error":"E08150","correct":"handler as RequestHandler!","incorrect":"handler as WebHandler!","explanation":"Injection fields must use abstract types. EK9 has singleton scope only, and the abstract type is the contract through which the singleton instance is accessed. See ek9 -h E08150 for details."},{"error":"E50001","correct":"response1 <- handler.handle(\"/users\")","incorrect":"response1XYZ <- handler.handle(\"/users\")","explanation":"Renaming the variable means later references to 'response1' become unresolved, triggering E50001. See ek9 -h E50001 for details."}],"companions":[]}
{"id":331,"category":"Dependency Injection","question":"How do I test components with injected dependencies?","url":"https://ek9.io/qa/QA0331.html","alternatePhrasings":["How do I mock injected components in EK9?","What is the EK9 equivalent of @MockBean?","How do I unit test EK9 components with DI?"],"answer":"EK9 components are tested by creating test applications with mock/stub registrations. The abstract/concrete pattern makes substitution natural.\n\nTEST APPLICATION PATTERN\nDefine a test application that registers test doubles instead of production components:\n  defines application\n    TestApp\n      register MockLogger() as Logger\n      register ServiceUnderTest() as Service\nThe ServiceUnderTest receives MockLogger through its injection field.\n\nWHY NO @MOCKBEAN\nSpring's @MockBean uses runtime reflection to replace beans in the application context. EK9 does not need this: you simply register a different concrete component against the same abstract type. The compiler validates the test wiring just as it validates production wiring.\n\nTEST DOUBLES AS COMPONENTS\nCreate concrete components that implement the abstract contract with test behaviour:\n  StubRepository is Repository\n    override find()\n      -> key as String\n      <- result as String: \"stub:\" + key\nThis stub is a real component with the same contract as the production implementation.\n\nADVANTAGES OVER SPRING TESTING\n1. NO CONTEXT LOADING: Spring tests often need @SpringBootTest which loads the entire context. EK9 test applications are lightweight.\n2. COMPILE-TIME VALIDATION: Even test wiring is validated at compile time.\n3. NO REFLECTION: Test doubles are concrete types, not proxy-based mocks.\n4. EXPLICIT SUBSTITUTION: You can see exactly what's replaced in the test application.\n\nSee Q155 for writing unit tests. See Q227 for compile-time validation. See Q324 for injection basics. See Q234 for component lifecycle.","ek9Example":"defines module qa.di.testing.injected\n\n  defines component\n\n    <?-\n      Abstract contract for the dependency.\n    -?>\n    EmailSender as abstract\n      send() as abstract\n        -> message as String\n        <- status as String?\n\n      default operator ?\n\n    <?-\n      Production implementation.\n    -?>\n    SmtpEmailSender is EmailSender\n      override send()\n        -> message as String\n        <- status as String: \"SMTP sent: \" + message\n\n      default operator ?\n\n    <?-\n      Test double: captures sent messages for verification.\n    -?>\n    StubEmailSender is EmailSender\n      override send()\n        -> message as String\n        <- status as String: \"STUB captured: \" + message\n\n      default operator ?\n\n    NotificationService as abstract\n      notifyUser() as abstract\n        -> userId as String\n        <- result as String?\n\n      default operator ?\n\n    DefaultNotificationService is NotificationService\n      sender as EmailSender!\n\n      override notifyUser()\n        -> userId as String\n        <- result <- String()\n        result: sender.send(\"Notification for \" + userId)\n\n      default operator ?\n\n  defines application\n\n    <?-\n      Production application: uses real email sender.\n    -?>\n    ProductionApp\n      register SmtpEmailSender() as EmailSender\n      register DefaultNotificationService() as NotificationService\n\n    <?-\n      Test application: substitutes stub email sender.\n    -?>\n    TestApp\n      register StubEmailSender() as EmailSender\n      register DefaultNotificationService() as NotificationService\n\n  defines program\n\n    ProductionDemo() with application of ProductionApp\n      stdout <- Stdout()\n\n      service as NotificationService!\n      result <- service.notifyUser(\"user-42\")\n      stdout.println(result)\n\n    TestDemo() with application of TestApp\n      stdout <- Stdout()\n\n      // === TEST APPLICATION SUBSTITUTES STUB FOR PRODUCTION ===\n\n      service as NotificationService!\n      result <- service.notifyUser(\"user-42\")\n      stdout.println(result)\n\n      stdout.println(\"Test double injected via test application\")","migrationContext":"Java Spring: @MockBean/@SpyBean for test doubles, @SpringBootTest for integration tests, Mockito for mocking. Guice: override bindings in test modules. .NET: replace services in IServiceCollection for tests. Python: unittest.mock, dependency_injector overrides. Go: interface-based testing, manual mock construction. EK9: test applications with substitute component registrations, compile-time validated test wiring, no reflection-based mocking.","keywords":["application","component","double","inject","migrate","mock","stub","substitute","test","unit","verify"],"primaryTopics":[],"typicalErrors":[{"error":"E08150","correct":"sender as EmailSender!","incorrect":"sender as SmtpEmailSender!","explanation":"Injection fields must use abstract types. This is what makes test substitution possible: both ProductionApp and TestApp register different concrete types against the same abstract EmailSender. See ek9 -h E08150 for details."},{"error":"E50001","correct":"result <- service.notifyUser(\"user-42\")","incorrect":"resultXYZ <- service.notifyUser(\"user-42\")","explanation":"Renaming the variable means later references to 'result' become unresolved, triggering E50001. See ek9 -h E50001 for details."}],"companions":[]}
{"id":332,"category":"Dependency Injection","question":"What is the runtime overhead of EK9 DI compared to frameworks?","url":"https://ek9.io/qa/QA0332.html","alternatePhrasings":["Is EK9 DI faster than Spring DI?","Does EK9 dependency injection add performance overhead?","How does EK9 DI performance compare to other languages?"],"answer":"EK9 DI has near-zero runtime overhead because all validation happens at compile time and injection is simple field assignment.\n\nSPRING DI OVERHEAD\n1. REFLECTION: Spring uses reflection to discover and inject beans. Reflection is 10-100x slower than direct field access.\n2. PROXY GENERATION: @Transactional, @Cacheable, and other annotations generate CGLIB proxies that add method call overhead.\n3. CONTEXT STARTUP: Spring ApplicationContext initialization can take seconds to minutes for large applications.\n4. CLASSPATH SCANNING: Component scanning examines every class on the classpath.\n\nQUARKUS/MICRONAUT OVERHEAD\nBoth reduce startup time by moving work to build time, but still have more runtime overhead than EK9 due to CDI spec compliance and scope management.\n\nEK9 DI OVERHEAD\n1. COMPILE-TIME VALIDATION: All validation happens during compilation, zero cost at runtime.\n2. SIMPLE FIELD ASSIGNMENT: Injection is direct field assignment during the prepare phase, equivalent to constructor assignment.\n3. NO REFLECTION: No runtime reflection, no proxy generation.\n4. NO SCOPE MANAGEMENT: Singleton-only means no scope resolution at each injection point.\n5. STARTUP: Component creation is sequential field assignment, not graph resolution.\n\nPERFORMANCE COMPARISON\nSpring: seconds to start, reflection overhead per injection, proxy overhead per annotated method call.\nQuarkus: sub-second start, reduced reflection, some proxy overhead.\nEK9: near-instant start, zero reflection, zero proxy overhead, direct field access.\n\nSee Q227 for compile-time validation. See Q234 for lifecycle. See Q328 for Quarkus comparison.","ek9Example":"defines module qa.di.overhead\n\n  defines component\n\n    Calculator as abstract\n      compute() as abstract\n        -> input as String\n        <- output as String?\n\n      default operator ?\n\n    FastCalculator is Calculator\n      override compute()\n        -> input as String\n        <- output as String: \"computed:\" + input\n\n      default operator ?\n\n    Aggregator as abstract\n      aggregate() as abstract\n        -> items as String\n        <- total as String?\n\n      default operator ?\n\n    SimpleAggregator is Aggregator\n      calc as Calculator!\n\n      override aggregate()\n        -> items as String\n        <- total <- String()\n        total: calc.compute(items)\n\n      default operator ?\n\n  defines application\n\n    ZeroOverheadApp\n      register FastCalculator() as Calculator\n      register SimpleAggregator() as Aggregator\n\n  defines program\n\n    OverheadDemo() with application of ZeroOverheadApp\n      stdout <- Stdout()\n\n      // === ZERO RUNTIME OVERHEAD: direct field access ===\n\n      aggregator as Aggregator!\n\n      result <- aggregator.aggregate(\"1,2,3\")\n      stdout.println(result)\n\n      stdout.println(\"No reflection, no proxies, no scope resolution\")","migrationContext":"Java Spring: reflection-based injection, CGLIB proxies, seconds-to-minutes startup, BeanPostProcessor overhead. Quarkus: build-time resolution, sub-second startup, reduced but present proxy overhead. Micronaut: compile-time DI, minimal reflection. Go: no DI overhead (manual wiring). Rust: no DI overhead (manual construction). EK9: compile-time validation, simple field assignment, zero reflection, zero proxy, near-instant startup.","keywords":["compile","cost","dependency","inject","migrate","overhead","performance","proxy","reflection","runtime","speed","startup","zero"],"primaryTopics":[],"typicalErrors":[{"error":"E08150","correct":"calc as Calculator!","incorrect":"calc as FastCalculator!","explanation":"Injection fields must use abstract types. Direct field assignment during the prepare phase resolves abstract types to concrete implementations with zero reflection overhead. See ek9 -h E08150 for details."},{"error":"E08200","correct":"register FastCalculator() as Calculator\n      register SimpleAggregator() as Aggregator","incorrect":"register SimpleAggregator() as Aggregator\n      register FastCalculator() as Calculator","explanation":"SimpleAggregator injects Calculator, so Calculator must be registered first. Registration order determines creation order in the prepare phase. See ek9 -h E08200 for details."}],"companions":[]}
{"id":333,"category":"Dependency Injection","question":"How do I manage transactions with try-with-resources in EK9?","url":"https://ek9.io/qa/QA0333.html","alternatePhrasings":["How do I use the Transaction trait with try-with-resources?","What is the simplest transaction pattern in EK9?","How does operator close work with transactions?"],"answer":"The simplest and most recommended transaction pattern uses try-with-resources with a type implementing the Transaction trait. Transaction extends Closeable, so operator close provides automatic cleanup.\n\nBASIC PATTERN\nCreate a transaction in the try header, use it in the body, commit on success:\n  try\n    -> txn <- DatabaseTransaction(connection)\n    updateRecords(txn)\n    txn.commit()\n  catch\n    -> ex as Exception\n    handleError(ex)\nWhen the try scope exits, operator close is called automatically.\n\nAUTO-ROLLBACK SAFETY NET\nThe operator close implementation can check isCommitted() and rollback if the transaction was not committed. This prevents partial commits when exceptions occur between operations.\n\nWHY THIS PATTERN IS BEST\n1. TRANSACTION SCOPE IS VISIBLE: the try block clearly delimits transaction boundaries\n2. AUTOMATIC CLEANUP: operator close is called regardless of how the scope exits\n3. EXCEPTION SAFE: exceptions trigger automatic cleanup, no finally block needed\n4. AI FRIENDLY: AI can see the transaction scope and generate correct code\n5. SIMILAR TO RUST: mirrors Diesel's RAII pattern and Go's defer\n\nCOMPARISON WITH SPRING\nSpring @Transactional hides the transaction boundary. Self-invocation bypasses the proxy. Checked exceptions silently commit. EK9's try-with-resources has none of these failure modes.\n\nSee Q137 for try-with-resources basics. See Q134 for try/catch patterns. See Q334 for delegation pattern. See Q335 for callback pattern. See Q338 for partial commit prevention.","ek9Example":"defines module qa.di.transaction.try.resources\n\n  defines trait\n\n    <?-\n      Connection abstraction for the example.\n    -?>\n    Connection\n      execute()\n        -> statement as String\n        <- result as String?\n\n  defines class\n\n    <?-\n      A concrete transaction that tracks commit state.\n      Implements Transaction trait (which extends Closeable).\n    -?>\n    DatabaseTransaction with trait of Transaction\n      connectionName <- String()\n      committed <- false\n\n      DatabaseTransaction()\n        -> connectionName as String\n        this.connectionName: connectionName\n\n      executeUpdate()\n        -> statement as String\n        <- result as String: `${connectionName}: ${statement}`\n\n      override commit()\n        committed: true\n\n      override rollback()\n        committed: false\n\n      override isCommitted() as pure\n        <- rtn as Boolean: committed\n\n      override operator close as pure\n        stdout <- Stdout()\n        stdout.println(\"Close: committed=\" + $committed)\n\n      override operator ? as pure\n        <- rtn <- true\n\n  defines program\n\n    TransactionTryResourcesDemo()\n      stdout <- Stdout()\n\n      // === TRY-WITH-RESOURCES: most explicit pattern ===\n\n      stdout.println(\"=== Successful transaction ===\")\n      try\n        -> txn <- DatabaseTransaction(\"primary-db\")\n        result1 <- txn.executeUpdate(\"UPDATE accounts SET balance = 100\")\n        stdout.println(result1)\n        result2 <- txn.executeUpdate(\"INSERT INTO audit_log VALUES ('transfer')\")\n        stdout.println(result2)\n        txn.commit()\n      catch\n        -> ex as Exception\n        stdout.println(\"Error: \" + $ex)\n\n      // === UNCOMMITTED TRANSACTION: close called without commit ===\n\n      stdout.println(\"=== Uncommitted transaction ===\")\n      try\n        -> txn <- DatabaseTransaction(\"secondary-db\")\n        result3 <- txn.executeUpdate(\"UPDATE inventory SET count = 0\")\n        stdout.println(result3)\n        // No commit — operator close sees committed=false\n      catch\n        -> ex as Exception\n        stdout.println(\"Error: \" + $ex)","migrationContext":"Java Spring: @Transactional annotation with proxy-based AOP, seven silent failure modes. Go: defer tx.Rollback() after tx, err := db.Begin(). Rust: conn.transaction(|txn| { ... }) RAII closure. C#: using (var scope = new TransactionScope()) { ... scope.Complete(); }. Python: with conn.begin() as txn: .... EK9: try -> txn <- Transaction() with operator close for auto-cleanup, isCommitted() check in close for safety.","keywords":["automatic","cleanup","close","commit","dependency","inject","migrate","resources","rollback","safety","scope","transaction","try"],"primaryTopics":[],"typicalErrors":[{"error":"E08180","correct":"connectionName <- String()","incorrect":"connectionName as String","explanation":"Class fields must be initialized inline at declaration. Declaring a field without initialization produces a field-not-initialized error unless the field is an injection point ('!'). See ek9 -h E08180 for details."}],"companions":[]}
{"id":334,"category":"Dependency Injection","question":"How do I add transactions with trait delegation in EK9?","url":"https://ek9.io/qa/QA0334.html","alternatePhrasings":["How do I use the decorator pattern for transactions in EK9?","Can I wrap a service with transaction management in EK9?","What is trait delegation for transaction handling?"],"answer":"EK9's 'by delegate' syntax wraps a service with transaction management without modifying the original service. This is the Decorator pattern applied to transactions.\n\nDELEGATION PATTERN\nDefine a trait for the service contract, then create a delegating wrapper:\n  TransactionalService with trait of Service by delegate\n    delegate as Service: BasicService()\n    override process()\n      -> input as String\n      <- result <- String()\n      // wrap with transaction logic\n      result: delegate.process(input)\n\nHOW IT WORKS\n1. 'by delegate' tells the compiler to generate delegation methods for all abstract methods\n2. You override only the methods that need transaction wrapping\n3. Non-overridden methods pass through directly to the delegate\n4. The original service is unaware of transaction management\n\nADVANTAGES\n1. SEPARATION OF CONCERNS: business logic and transaction management are separate\n2. COMPOSABLE: multiple decorators can be stacked (logging, caching, transactions)\n3. TESTABLE: test the service without transactions, test the wrapper separately\n4. OPEN/CLOSED: add transactions without modifying existing service code\n\nSIMILAR TO C# MIDDLEWARE\nThis pattern mirrors C#'s middleware pipeline where each layer wraps the next. But in EK9, the delegation is compile-time generated and type-safe.\n\nSee Q210 for trait delegation basics. See Q333 for try-with-resources pattern. See Q266 for cross-cutting concerns. See Q335 for callback pattern.","ek9Example":"defines module qa.di.transaction.decorator\n\n  defines trait\n\n    <?-\n      Service contract as a trait (required for delegation).\n    -?>\n    OrderService\n      processOrder() as abstract\n        -> orderId as String\n        <- result as String?\n\n      cancelOrder() as abstract\n        -> orderId as String\n        <- result as String?\n\n  defines class\n\n    <?-\n      Concrete service with business logic only.\n      No transaction awareness.\n    -?>\n    BasicOrderService with trait of OrderService\n      override processOrder()\n        -> orderId as String\n        <- result as String: \"Processed: \" + orderId\n\n      override cancelOrder()\n        -> orderId as String\n        <- result as String: \"Cancelled: \" + orderId\n\n      default operator ?\n\n    <?-\n      Transactional wrapper using delegation.\n      Only processOrder gets transaction wrapping.\n      cancelOrder passes through to delegate unchanged.\n    -?>\n    TransactionalOrderService with trait of OrderService by delegate\n      delegate as OrderService: BasicOrderService()\n\n      TransactionalOrderService()\n        -> service as OrderService\n        this.delegate: service\n\n      override processOrder()\n        -> orderId as String\n        <- result <- String()\n        stdout <- Stdout()\n        stdout.println(\"BEGIN transaction for \" + orderId)\n        result: delegate.processOrder(orderId)\n        stdout.println(\"COMMIT transaction for \" + orderId)\n\n      default operator ?\n\n  defines program\n\n    TransactionDecoratorDemo()\n      stdout <- Stdout()\n\n      // === DELEGATION: wrap service with transaction ===\n\n      basicService <- BasicOrderService()\n      transactionalService <- TransactionalOrderService(basicService)\n\n      // processOrder is wrapped with transaction\n      result1 <- transactionalService.processOrder(\"ORD-001\")\n      stdout.println(result1)\n\n      // cancelOrder passes through unchanged via delegation\n      result2 <- transactionalService.cancelOrder(\"ORD-002\")\n      stdout.println(result2)\n\n      stdout.println(\"Delegation adds transactions without modifying service\")","migrationContext":"Java Spring: @Transactional on service methods, proxy-based decoration. C#: middleware pipeline, HttpClient DelegatingHandler. Go: middleware functions wrapping handlers. Rust: tower middleware layers. Python: decorators (@transactional). EK9: 'by delegate' for compile-time generated delegation, override specific methods for transaction wrapping, type-safe composition.","keywords":["abstract","compose","decorator","delegate","dependency","inject","layer","middleware","migrate","open","override","pattern","separation","service","trait","virtual","wrap"],"primaryTopics":[],"typicalErrors":[{"error":"E08180","correct":"delegate as OrderService: BasicOrderService()","incorrect":"delegate as OrderService","explanation":"Class fields must be initialized at declaration. The delegate field needs a default value. Only injection fields ('!') can be left uninitialized because the DI framework provides their values. See ek9 -h E08180 for details."}],"companions":[]}
{"id":335,"category":"Dependency Injection","question":"How do I use higher-order functions for transaction management?","url":"https://ek9.io/qa/QA0335.html","alternatePhrasings":["Can I pass business logic to a transaction wrapper function?","What is the callback pattern for transactions in EK9?","How do I create a reusable transaction execution function?"],"answer":"Pass business logic as a function to a transaction wrapper. This creates a reusable transaction execution framework inspired by Rust/Diesel's closure pattern.\n\nCALLBACK PATTERN\nDefine a function that accepts a business operation and wraps it in a transaction:\n  executeInTransaction()\n    -> operation as BusinessOperation\n    <- result as String?\n    try\n      -> txn <- Transaction()\n      result: operation(\"context\")\n      txn.commit()\n    catch\n      -> ex as Exception\n      result: \"Failed\"\n\nHOW IT WORKS\n1. The transaction wrapper creates and manages the transaction\n2. Business logic is passed as a function (Consumer, Function, etc.)\n3. The wrapper calls the business logic within the transaction scope\n4. Commit happens after successful execution, rollback on exception\n\nADVANTAGES\n1. REUSABLE: one transaction wrapper serves all business operations\n2. CONSISTENT: transaction management logic is centralized\n3. TESTABLE: test business logic independently of transaction management\n4. EXPLICIT: transaction scope is visible at the call site\n\nSIMILAR TO RUST DIESEL\nRust's conn.transaction(|txn| { ... }) pattern. EK9 achieves the same with higher-order functions.\n\nSee Q56 for higher-order functions. See Q52 for dynamic functions. See Q333 for try-with-resources pattern. See Q336 for closure capture pattern.","ek9Example":"defines module qa.di.transaction.callback\n\n  defines class\n\n    SimpleTransaction with trait of Transaction\n      label <- String()\n      committed <- false\n\n      SimpleTransaction()\n        -> label as String\n        this.label: label\n\n      override commit()\n        committed: true\n\n      override rollback()\n        committed: false\n\n      override isCommitted() as pure\n        <- rtn as Boolean: committed\n\n      override operator close as pure\n        stdout <- Stdout()\n        if committed\n          stdout.println($label + \": committed on close\")\n        else\n          stdout.println($label + \": rolled back on close\")\n\n      override operator ? as pure\n        <- rtn <- true\n\n  defines function\n\n    <?-\n      Business operation type: accepts a label and returns a result.\n    -?>\n    BusinessOperation() as abstract\n      -> context as String\n      <- result as String?\n\n    <?-\n      Concrete operation: create user.\n    -?>\n    CreateUserOp() is BusinessOperation\n      -> context as String\n      <- result as String: \"Created user in \" + context\n\n    <?-\n      Concrete operation: update inventory.\n    -?>\n    UpdateInventoryOp() is BusinessOperation\n      -> context as String\n      <- result as String: \"Updated inventory in \" + context\n\n    <?-\n      Reusable transaction wrapper: executes any operation in a transaction.\n    -?>\n    executeInTransaction()\n      -> operation as BusinessOperation\n      <- result <- String()\n      try\n        -> txn <- SimpleTransaction(\"auto-txn\")\n        result: operation(\"within-transaction\")\n        txn.commit()\n      catch\n        -> ex as Exception\n        result: \"Transaction failed: \" + $ex\n\n  defines program\n\n    TransactionCallbackDemo()\n      stdout <- Stdout()\n\n      // === CALLBACK PATTERN: pass business logic to wrapper ===\n\n      result1 <- executeInTransaction(CreateUserOp)\n      stdout.println(result1)\n\n      result2 <- executeInTransaction(UpdateInventoryOp)\n      stdout.println(result2)\n\n      stdout.println(\"Reusable wrapper executes any operation in a transaction\")","migrationContext":"Rust/Diesel: conn.transaction(|txn| { ... }) closure pattern. Kotlin/Exposed: transaction { ... } block. Go: custom withTransaction(func(tx *Tx) error) pattern. Java: Spring TransactionTemplate.execute(callback). C#: custom ExecuteInTransaction(Action) pattern. EK9: higher-order functions with dynamic function callbacks, captured closure state, try-with-resources inside wrapper.","keywords":["callback","closure","consumer","dependency","execute","function","higher","inject","migrate","order","pattern","reusable","wrapper"],"primaryTopics":[],"typicalErrors":[{"error":"E08180","correct":"label <- String()","incorrect":"label as String","explanation":"Class fields must be initialized at declaration. Leaving a field without initialization and without the injection suffix '!' produces a field-not-initialized error. See ek9 -h E08180 for details."}],"companions":[]}
{"id":336,"category":"Dependency Injection","question":"How do dynamic functions solve transaction API pollution?","url":"https://ek9.io/qa/QA0336.html","alternatePhrasings":["How do I avoid passing transaction through every function?","What is the closure capture pattern for transactions?","How do dynamic functions prevent API pollution in EK9?"],"answer":"Dynamic functions capture transaction state in closures, keeping downstream APIs clean. This solves Go's #1 complaint about explicit transaction management.\n\nTHE PROBLEM: API POLLUTION\nIn Go, if processOrder() needs a transaction, and it calls validateInventory(), which calls checkWarehouse(), ALL three functions must accept a transaction parameter. This creates massive API surface bloat.\n\nEK9'S SOLUTION: CLOSURE CAPTURE\nCreate a dynamic function that captures the transaction:\n  createProcessor()\n    -> txn as Transaction\n    <- processor as Processor\n    processor: () is Processor as function ...\nDownstream functions (doWork, etc.) have clean APIs with no transaction parameter.\n\nHOW IT WORKS\n1. Factory function receives the transaction context\n2. Dynamic function captures the context in its closure\n3. Downstream functions are called without transaction parameters\n4. Transaction is managed by the factory caller, not the interior functions\n\nWHY THIS IS TRANSFORMATIVE\n1. EXPLICIT AT THE BOUNDARY: the factory clearly shows transaction capture\n2. CLEAN INTERIOR: downstream APIs are unpolluted by transaction plumbing\n3. TYPE SAFE: the captured state is typed, not a context value bag\n4. TESTABLE: downstream functions can be tested without any transaction setup\n\nCOMBINES GO AND RUST ADVANTAGES\nGo's explicitness (transaction is a real value) plus Rust's closure elegance (captured state is invisible to callers).\n\nSee Q53 for closure capture basics. See Q52 for dynamic functions. See Q333 for try-with-resources. See Q335 for callback pattern.","ek9Example":"defines module qa.di.transaction.closure\n\n  defines function\n\n    <?-\n      Abstract processor with clean API: no transaction parameter.\n    -?>\n    OrderProcessor() as abstract\n      -> orderId as String\n      <- result as String?\n\n    <?-\n      Downstream function: no transaction parameter needed.\n    -?>\n    validateOrder()\n      -> orderId as String\n      <- isValid as Boolean: orderId?\n\n    <?-\n      Another downstream function: clean API.\n    -?>\n    formatConfirmation()\n      -> orderId as String\n      <- confirmation as String: \"Confirmed: \" + orderId\n\n    <?-\n      Downstream: builds final result with captured label.\n    -?>\n    buildResult()\n      ->\n        txnLabel as String\n        orderId as String\n      <- result <- String()\n      valid <- validateOrder(orderId)\n      if valid\n        confirmation <- formatConfirmation(orderId)\n        result: `${txnLabel} | ${confirmation}`\n      else\n        result: `${txnLabel} | validation failed`\n\n  defines program\n\n    ClosureCaptureDemo()\n      stdout <- Stdout()\n\n      // === CLOSURE CAPTURE CONCEPT ===\n      // In full EK9, a factory function would return a dynamic function\n      // that captures txnLabel in its closure.\n      // Here we demonstrate the same concept with explicit function calls.\n\n      txnLabel <- \"TXN-001\"\n\n      // Downstream functions have clean APIs - no transaction parameter\n      result1 <- buildResult(txnLabel, \"ORD-100\")\n      stdout.println(result1)\n\n      result2 <- buildResult(txnLabel, \"ORD-200\")\n      stdout.println(result2)\n\n      stdout.println(\"Transaction context at boundary, clean APIs inside\")","migrationContext":"Go: context.Context + tx parameter pollution through every function in the chain. Rust: closures capture state naturally. Java: ThreadLocal for ambient state (fragile). C#: TransactionScope ambient (fragile). Python: contextvars for ambient state. EK9: dynamic functions capture transaction in closure, downstream APIs stay clean, explicit at boundary, invisible in interior.","keywords":["anonymous","api","capture","clean","closure","dependency","dynamic","enclosing","factory","function","inject","migrate","pollution","scope"],"primaryTopics":[],"typicalErrors":[{"error":"E50001","correct":"result1 <- buildResult(txnLabel, \"ORD-100\")","incorrect":"result1XYZ <- buildResult(txnLabel, \"ORD-100\")","explanation":"Renaming the variable means later references to 'result1' become unresolved, triggering E50001. See ek9 -h E50001 for details."},{"error":"E50001","correct":"txnLabel <- \"TXN-001\"","incorrect":"txnLabelXYZ <- \"TXN-001\"","explanation":"Renaming the variable means later references to 'txnLabel' become unresolved, triggering E50001. See ek9 -h E50001 for details."}],"companions":[]}
{"id":337,"category":"Dependency Injection","question":"Can I use aspects for transactions like Spring @Transactional?","url":"https://ek9.io/qa/QA0337.html","alternatePhrasings":["Does EK9 support AOP-based transaction management?","How do I use aspects for cross-cutting transaction concerns?","What is the AOP approach to transactions in EK9?"],"answer":"Yes, EK9 supports AOP-based transaction management through its aspect system. However, this pattern should be used with caution because it makes transaction boundaries invisible at the call site.\n\nASPECT-BASED TRANSACTIONS\nDefine a TransactionAspect that extends Aspect:\n  TransactionAspect extends Aspect\n    override beforeAdvice()\n      -> joinPoint as JoinPoint\n      <- rtn as PreparedMetaData: PreparedMetaData(joinPoint)\n    override afterAdvice()\n      -> preparedMetaData as PreparedMetaData\nRegister with: register Service() as ServiceType with aspect of TransactionAspect()\n\nHOW IT WORKS\n1. beforeAdvice() runs before the advised method (start transaction)\n2. The advised method executes within the transaction context\n3. afterAdvice() runs after the advised method (commit or rollback)\n4. The aspect is registered in the application definition\n\nCAUTION: INVISIBLE BEHAVIOUR\nThis pattern has the same problem as Spring @Transactional: the transaction is invisible at the call site. A developer reading service.process(order) cannot see that a transaction exists. This is acceptable for truly uniform cross-cutting concerns but dangerous for business-critical transaction logic.\n\nWHEN TO USE ASPECTS\n1. ALL methods on a service need identical transaction behavior\n2. Transaction logic is truly uniform (no method-specific rollback rules)\n3. The team understands that transactions are managed by the aspect\n4. Auditing, logging, or metrics that are purely cross-cutting\n\nWHEN TO PREFER EXPLICIT PATTERNS\n1. Different methods need different transaction strategies\n2. Transaction scope doesn't align with method boundaries\n3. AI is generating the code (AI cannot see aspect behavior)\n4. Debugging transaction issues is a concern\n\nSee Q114 for aspect basics. See Q266 for cross-cutting concerns. See Q333 for try-with-resources (recommended). See Q334 for delegation pattern.","ek9Example":"defines module qa.di.transaction.aspect\n\n  defines component\n\n    PaymentService as abstract\n      processPayment() as abstract\n        -> amount as String\n        <- receipt as String?\n\n      default operator ?\n\n    BasicPaymentService is PaymentService\n      override processPayment()\n        -> amount as String\n        <- receipt as String: \"Payment processed: \" + amount\n\n      default operator ?\n\n  defines class\n\n    <?-\n      Transaction aspect: wraps advised methods with transaction behavior.\n    -?>\n    TransactionAspect extends Aspect\n      label <- String()\n\n      TransactionAspect()\n        -> label as String\n        this.label: label\n\n      override beforeAdvice()\n        -> joinPoint as JoinPoint\n        <- rtn as PreparedMetaData: PreparedMetaData(joinPoint)\n        Stdout().println(`${label} BEGIN: ${joinPoint.componentName()}.${joinPoint.methodName()}`)\n\n      override afterAdvice()\n        -> preparedMetaData as PreparedMetaData\n        joinPoint <- preparedMetaData.joinPoint()\n        Stdout().println(`${label} COMMIT: ${joinPoint.componentName()}.${joinPoint.methodName()}`)\n\n      default operator ?\n\n  defines application\n\n    TransactionalAspectApp\n      register BasicPaymentService() as PaymentService with aspect of TransactionAspect(\"TXN\")\n\n  defines program\n\n    TransactionAspectDemo() with application of TransactionalAspectApp\n      stdout <- Stdout()\n\n      // === AOP: transaction is invisible at this call site ===\n\n      payment as PaymentService!\n\n      receipt <- payment.processPayment(\"99.95\")\n      stdout.println(receipt)\n\n      stdout.println(\"Aspect adds transaction, but behavior is invisible here\")\n      stdout.println(\"Prefer explicit patterns for critical business logic\")","migrationContext":"Java Spring: @Transactional annotation, proxy-based AOP, seven silent failure modes. AspectJ: compile-time weaving for more reliable AOP. Go: no AOP support, explicit middleware only. Rust: no AOP support. C#: Castle DynamicProxy or middleware. EK9: Aspect class with beforeAdvice/afterAdvice, 'with aspect of' registration, compile-time wiring, but invisible behavior at call site.","keywords":["advice","after","aop","aspect","before","caution","cross","cross-cutting","cutting","dependency","inject","invisible","migrate","transactional","weave"],"primaryTopics":[],"typicalErrors":[{"error":"E08150","correct":"payment as PaymentService!","incorrect":"payment as BasicPaymentService!","explanation":"Injection fields must use abstract component types. The aspect is applied through the application registration, and the program injects the abstract type. See ek9 -h E08150 for details."},{"error":"E08180","correct":"label <- String()","incorrect":"label as String","explanation":"Class fields must be initialized at declaration. Without initialization and without the '!' injection suffix, the compiler reports a field-not-initialized error. See ek9 -h E08180 for details."}],"companions":[]}
{"id":338,"category":"Dependency Injection","question":"How does EK9 prevent partial transaction commits?","url":"https://ek9.io/qa/QA0338.html","alternatePhrasings":["How do I ensure all-or-nothing transactions in EK9?","What prevents half-committed data in EK9?","How does EK9 handle transaction atomicity?"],"answer":"EK9 prevents partial commits through three structural mechanisms: operator close auto-cleanup, no early return, and the isCommitted() safety check.\n\nMECHANISM 1: OPERATOR CLOSE AUTO-CLEANUP\nTransaction extends Closeable. When used with try-with-resources, operator close is called automatically when the scope exits. Implementations check isCommitted() in close and rollback if not committed. This prevents the 'forgot to rollback' bug.\n\nMECHANISM 2: NO EARLY RETURN\nEK9 has no return statement. You cannot return early from the middle of a transaction, which is one of the most common causes of partial commits in other languages. The transaction scope always runs to completion or throws.\n\nMECHANISM 3: isCommitted() SAFETY CHECK\nThe isCommitted() method returns a tri-state Boolean:\n- Unset: transaction state unknown (default, not yet committed or rolled back)\n- True: committed\n- False: explicitly not committed (active transaction)\nOperator close can check this and take appropriate action.\n\nCOMMON PARTIAL COMMIT SCENARIOS PREVENTED\n1. EXCEPTION BETWEEN OPERATIONS: operator close triggers rollback automatically\n2. EARLY RETURN: impossible in EK9 (no return statement)\n3. FORGOT TO COMMIT: operator close detects uncommitted state\n4. FORGOT TO ROLLBACK: operator close handles cleanup\n\nSPRING SILENT COMMIT BUG\nIn Spring, checked exceptions silently commit the transaction. A method that throws IOException commits partial data. EK9's operator close checks commit state regardless of exception type.\n\nSee Q333 for try-with-resources pattern. See Q144 for no break/continue/return. See Q134 for exception handling. See Q137 for try-with-resources basics.","ek9Example":"defines module qa.di.transaction.no.partial\n\n  defines class\n\n    SafeTransaction with trait of Transaction\n      identifier <- String()\n      committed <- false\n      operations <- 0\n\n      SafeTransaction()\n        -> identifier as String\n        this.identifier: identifier\n\n      recordOperation()\n        -> description as String\n        <- status as String: `${identifier} op#${operations}: ${description}`\n        operations: operations + 1\n\n      override commit()\n        committed: true\n\n      override rollback()\n        committed: false\n        operations: 0\n\n      override isCommitted() as pure\n        <- rtn as Boolean: committed\n\n      <?-\n        Safety net: auto-rollback if not committed.\n      -?>\n      override operator close as pure\n        stdout <- Stdout()\n        if committed\n          stdout.println(`${identifier}: close after commit (${operations} ops)`)\n        else\n          stdout.println(`${identifier}: ROLLBACK on close (${operations} uncommitted ops)`)\n\n      override operator ? as pure\n        <- rtn <- true\n\n  defines program\n\n    NoPartialCommitDemo()\n      stdout <- Stdout()\n\n      // === SUCCESSFUL: all operations committed ===\n\n      stdout.println(\"=== Committed transaction ===\")\n      try\n        -> txn <- SafeTransaction(\"TXN-A\")\n        status1 <- txn.recordOperation(\"debit account\")\n        stdout.println(status1)\n        status2 <- txn.recordOperation(\"credit account\")\n        stdout.println(status2)\n        txn.commit()\n      catch\n        -> ex as Exception\n        stdout.println(\"Error: \" + $ex)\n\n      // === UNCOMMITTED: operator close auto-rollbacks ===\n\n      stdout.println(\"=== Uncommitted transaction ===\")\n      try\n        -> txn <- SafeTransaction(\"TXN-B\")\n        status3 <- txn.recordOperation(\"update inventory\")\n        stdout.println(status3)\n        status4 <- txn.recordOperation(\"send notification\")\n        stdout.println(status4)\n        // No commit — operator close detects and reports\n      catch\n        -> ex as Exception\n        stdout.println(\"Error: \" + $ex)\n\n      stdout.println(\"Operator close prevents partial commits automatically\")","migrationContext":"Java Spring: checked exceptions silently commit, @Transactional(rollbackFor) needed for explicit control. Go: must remember defer tx.Rollback() after every tx.Begin(). Rust: Drop trait ensures cleanup but requires explicit commit. C#: TransactionScope.Complete() must be called explicitly. Python: context manager __exit__ handles cleanup. EK9: operator close auto-cleanup, no return prevents mid-transaction exit, isCommitted() tri-state check, structural prevention of partial commits.","keywords":["atomicity","cleanup","close","commit","dependency","inject","migrate","partial","prevent","return","rollback","safety","structural"],"primaryTopics":[],"typicalErrors":[{"error":"E08180","correct":"identifier <- String()","incorrect":"identifier as String","explanation":"Class fields must be initialized at declaration. Without initialization or the injection suffix '!', the compiler reports a field-not-initialized error. See ek9 -h E08180 for details."}],"companions":[]}
{"id":339,"category":"Dependency Injection","question":"Which transaction management pattern should I use in EK9?","url":"https://ek9.io/qa/QA0339.html","alternatePhrasings":["How do I choose between EK9 transaction patterns?","What is the best transaction pattern for my use case?","When should I use try-with-resources vs delegation vs aspects for transactions?"],"answer":"EK9 offers five transaction patterns. Choose based on your specific requirements using this decision guide.\n\nDEFAULT CHOICE: TRY-WITH-RESOURCES (Pattern 1)\nUse for: most CRUD operations, single-function transaction scope\nWhy: most explicit, easiest to debug, AI generates correctly\nCode: try -> txn <- Transaction() ... txn.commit()\n\nCONTEXT PASSING (Pattern 2)\nUse for: multiple functions need shared state beyond just the transaction\nWhy: typed context record carries transaction + audit log + correlation ID\nWhen: call chains need rich context, not just the transaction\n\nDELEGATION/DECORATOR (Pattern 3)\nUse for: adding transactions to existing services without modification\nWhy: separation of concerns, composable, testable\nWhen: layered architecture, multiple cross-cutting concerns\n\nAOP ASPECT (Pattern 4)\nUse for: truly uniform transaction behavior across many services\nCaution: makes transactions invisible at call site\nWhen: team understands AOP, behavior is identical for all advised methods\n\nCALLBACK/HIGHER-ORDER (Pattern 5)\nUse for: reusable transaction execution framework\nWhy: centralizes transaction management, any operation can use it\nWhen: infrastructure code, many different operations need same wrapping\n\nDYNAMIC FUNCTIONS (combine with any pattern)\nUse for: preventing API pollution in deep call chains\nWhy: captures transaction in closure, keeps downstream APIs clean\nWhen: transaction scope is wide but most functions do not need direct access\n\nDECISION FLOWCHART\n1. Is the transaction scope a single function? -> Pattern 1 (try-with-resources)\n2. Do multiple functions need shared context? -> Pattern 2 (context record)\n3. Are you wrapping existing services? -> Pattern 3 (delegation)\n4. Is behavior truly uniform across all services? -> Pattern 4 (aspect, with caution)\n5. Do you want a reusable framework? -> Pattern 5 (callback)\n6. Is API pollution a concern? -> Add dynamic functions to any pattern\n\nSee Q333 for Pattern 1. See Q334 for Pattern 3. See Q335 for Pattern 5. See Q336 for dynamic functions. See Q337 for Pattern 4. See Q340 for AI-friendliness analysis.","ek9Example":"defines module qa.di.transaction.selection\n\n  defines class\n\n    MiniTransaction with trait of Transaction\n      identifier <- String()\n      committed <- false\n\n      MiniTransaction()\n        -> identifier as String\n        this.identifier: identifier\n\n      override commit()\n        committed: true\n\n      override isCommitted() as pure\n        <- rtn as Boolean: committed\n\n      override operator close as pure\n        stdout <- Stdout()\n        stdout.println(`${identifier} closed (committed=${committed})`)\n\n      override operator ? as pure\n        <- rtn <- true\n\n  defines program\n\n    PatternSelectionDemo()\n      stdout <- Stdout()\n\n      // === PATTERN 1: Try-with-resources (DEFAULT CHOICE) ===\n\n      stdout.println(\"=== Pattern 1: Try-with-resources ===\")\n      try\n        -> txn <- MiniTransaction(\"P1\")\n        stdout.println(\"Business logic here\")\n        txn.commit()\n      catch\n        -> ex as Exception\n        stdout.println(\"Error: \" + $ex)\n\n      // Most use cases are covered by Pattern 1 above\n      // Only use other patterns when Pattern 1 doesn't fit\n\n      stdout.println(\"Default: try-with-resources\")\n      stdout.println(\"Context passing: when multiple functions need shared state\")\n      stdout.println(\"Delegation: when wrapping existing services\")\n      stdout.println(\"Aspect: when behavior is truly uniform (use with caution)\")\n      stdout.println(\"Callback: when building reusable transaction framework\")","migrationContext":"Spring: primarily @Transactional (AOP), TransactionTemplate (callback). Go: explicit transaction passing (context pattern). Rust: Diesel closure pattern (callback). C#: TransactionScope (ambient), middleware (delegation). Kotlin: transaction { } block (callback). EK9: five patterns from most-explicit to most-implicit, try-with-resources recommended as default, dynamic functions solve API pollution uniquely.","keywords":["choose","comparison","decision","dependency","flowchart","guide","inject","pattern","recommendation","selection","use","when"],"primaryTopics":[],"typicalErrors":[{"error":"E08180","correct":"identifier <- String()","incorrect":"identifier as String","explanation":"Class fields must be initialized at declaration. The MiniTransaction class initializes all its fields inline. Omitting initialization without the '!' injection suffix is a compile-time error. See ek9 -h E08180 for details."}],"companions":[]}
{"id":340,"category":"Dependency Injection","question":"Why are EK9 transaction patterns better for AI-generated code?","url":"https://ek9.io/qa/QA0340.html","alternatePhrasings":["How do explicit transactions help AI code generation?","Why does AI struggle with Spring @Transactional?","What makes EK9 transaction patterns AI-friendly?"],"answer":"EK9's explicit transaction patterns are dramatically better for AI code generation because AI models are pattern matchers on syntax, and explicit patterns put transaction behavior into syntax.\n\nWHY AI STRUGGLES WITH @TRANSACTIONAL\n1. INVISIBLE BEHAVIOUR: @Transactional creates behavior with no syntactic presence at the call site. AI generating code that calls a @Transactional method has no signal that a transaction exists.\n2. SELF-INVOCATION TRAP: AI frequently generates code that calls @Transactional methods from within the same class, bypassing the proxy. This is the #1 Spring transaction bug.\n3. EXCEPTION CONFUSION: AI cannot reliably know which exceptions trigger rollback vs commit in Spring's model.\n4. CONFIGURATION DEPENDENCY: AI may generate @Transactional without @EnableTransactionManagement, producing silently broken code.\n\nWHY EK9 PATTERNS WIN FOR AI\n1. SYNTACTIC SIGNALS: try -> txn <- Transaction() puts the transaction directly in the code. AI can see it, reason about it, and generate correct interactions.\n2. COMPILER FEEDBACK: When AI generates incorrect transaction code, EK9's compiler provides specific error messages (via -E3) that guide correction.\n3. NO HIDDEN STATE: Every transaction pattern in EK9 has visible syntax. No proxies, no ambient state, no annotation magic.\n4. PATTERN MATCHING: AI excels at reproducing patterns it can see. EK9's try-with-resources transaction pattern is a clear, reproducible template.\n\nTHE DUAL-AUDIENCE SHIFT\nFrom 2027, code serves two audiences: humans and AI. Both need the same thing: visible behavior in syntax. Languages that hide behavior in annotations, proxies, and conventions are optimized for neither audience.\n\nCOMPILER AS AI CONTROL PROTOCOL\nEK9's compiler error cascade acts as an AI steering mechanism. Each error message is a course correction signal. For transactions:\n- Missing operator close implementation -> add it\n- Type mismatch on Transaction -> fix the type\n- Unused variable warning -> use or remove the transaction\nSilence means correctness across all dimensions.\n\nSee Q281 for AI code verification. See Q323 for AI and code quality. See Q333 for try-with-resources pattern. See Q339 for pattern selection guide.","ek9Example":"defines module qa.di.transaction.ai.friendly\n\n  defines class\n\n    AIFriendlyTransaction with trait of Transaction\n      label <- String()\n      committed <- false\n\n      AIFriendlyTransaction()\n        -> label as String\n        this.label: label\n\n      override commit()\n        committed: true\n\n      override isCommitted() as pure\n        <- rtn as Boolean: committed\n\n      override operator close as pure\n        stdout <- Stdout()\n        stdout.println(`CLOSE ${label} committed=${committed}`)\n\n      override operator ? as pure\n        <- rtn <- true\n\n  defines function\n\n    <?-\n      This function demonstrates the pattern AI generates best:\n      explicit try-with-resources with visible transaction scope.\n    -?>\n    processWithTransaction()\n      -> input as String\n      <- output <- String()\n\n      try\n        -> txn <- AIFriendlyTransaction(\"ai-txn\")\n        // AI can see the transaction scope clearly\n        // AI can see commit() is needed before scope exits\n        output: \"Processed: \" + input\n        txn.commit()\n      catch\n        -> ex as Exception\n        output: \"Failed: \" + $ex\n\n  defines program\n\n    AIFriendlyDemo()\n      stdout <- Stdout()\n\n      // === AI GENERATES THIS PATTERN CORRECTLY ===\n\n      result <- processWithTransaction(\"order-data\")\n      stdout.println(result)\n\n      // AI can see:\n      // 1. Transaction created in try header\n      // 2. Business logic in try body\n      // 3. commit() before scope exits\n      // 4. operator close handles cleanup\n      // No hidden behavior, no proxy magic, no annotation confusion\n\n      stdout.println(\"Explicit patterns: AI sees, AI generates correctly\")\n      stdout.println(\"Compiler catches mistakes via error cascade\")","migrationContext":"AI with Spring: generates @Transactional that silently fails (self-invocation, missing config, wrong exception handling). AI with Go: generates correct explicit code but with API pollution. AI with Rust: generates correct closure patterns. AI with EK9: generates correct try-with-resources patterns, compiler catches mistakes, error messages guide correction, explicit syntax matches AI's pattern-matching strength.","keywords":["ai","code","compiler","dependency","explicit","friendly","generate","inject","invisible","migrate","pattern","signal","syntax"],"primaryTopics":[],"typicalErrors":[{"error":"E08180","correct":"label <- String()","incorrect":"label as String","explanation":"Class fields must be initialized at declaration. AI code generators must ensure all fields have initial values. Only injection fields ('!') can omit initialization. See ek9 -h E08180 for details."}],"companions":[]}
{"id":536,"category":"Date, Time, and Duration","question":"How do I get the current date and time in EK9?","url":"https://ek9.io/qa/QA0536.html","alternatePhrasings":["How do I get today's date in EK9?","How do I get the current time in EK9?","What is the difference between Date() and Date().today()?"],"answer":"EK9 has three factory methods for 'now': Date().today(), Time().now(), and DateTime().now(). The critical distinction is that the no-arg constructor creates an UNSET value, not the current moment.\n\nGETTING THE CURRENT MOMENT\nDate().today() returns today's calendar date.\nTime().now() returns the current wall-clock time.\nDateTime().now() returns the current date, time, and timezone.\nDateTime().today() is an alias for DateTime().now().\n\nTHE UNSET CONSTRUCTOR TRAP\nDate() creates an unset date (not today). Time() creates an unset time (not now). DateTime() creates an unset DateTime (not now). This follows EK9's tri-state design (see Q29): every type can be present-but-unset, which is different from absent and from set-with-value.\n\nWhy? Because 'no date specified yet' is a legitimate state. Think of a form where the user has not entered their birth date. You need to distinguish 'not entered' from 'today' from 'some specific date'.\n\nPATTERN: GUARD AGAINST UNSET\nSince the factory methods return new set values, you can use guard patterns:\n  if today <- Date().today()\n    stdout.println(`Today is ${today}`)\nThis always executes because today() always returns a set value.\n\nSee Q31 for Date and Time basics. See Q92 for DateTime with timezones. See Q29 for tri-state semantics. See Q543 for Unix timestamps. See Q544 for elapsed time benchmarking.","ek9Example":"defines module qa.current.date.time\n\n  defines program\n    CurrentDateTimeDemo()\n      stdout <- Stdout()\n\n      // Date() is UNSET - not today\n      unsetDate <- Date()\n      stdout.println(`Date() isSet: ${unsetDate?}`)\n\n      // Date().today() gets the current date\n      currentDate <- Date().today()\n      stdout.println(`Today: ${currentDate}`)\n\n      // Time() is UNSET - not now\n      unsetTime <- Time()\n      stdout.println(`Time() isSet: ${unsetTime?}`)\n\n      // Time().now() gets the current time\n      currentTime <- Time().now()\n      stdout.println(`Now: ${currentTime}`)\n\n      // DateTime() is UNSET - not now\n      unsetDT <- DateTime()\n      stdout.println(`DateTime() isSet: ${unsetDT?}`)\n\n      // DateTime().now() gets the current date+time+timezone\n      currentDT <- DateTime().now()\n      stdout.println(`Now: ${currentDT}`)\n\n      // DateTime().today() is an alias for now()\n      todayDT <- DateTime().today()\n      stdout.println(`Today: ${todayDT}`)\n\n      // Guard pattern - always executes because today() returns set value\n      if today <- Date().today()\n        stdout.println(`Guard confirmed today: ${today}`)\n\n      // All three are set\n      require currentDate?\n      require currentTime?\n      require currentDT?\n\n      // The unset ones are not\n      require ~unsetDate?\n      require ~unsetTime?\n      require ~unsetDT?","migrationContext":"Java: LocalDate.now(), LocalTime.now(), ZonedDateTime.now() static factory methods. Python: datetime.date.today(), datetime.datetime.now(). JavaScript: new Date() gives current moment (confusing: constructor IS the factory). Go: time.Now(). Rust: chrono::Local::now(). EK9: Date().today(), Time().now(), DateTime().now() factory methods; no-arg constructor creates unset, not current.","keywords":["clock","current","date","datetime","duration","factory","now","time","timezone","today","unset"],"primaryTopics":["current date time","now","today"],"typicalErrors":[{"error":"E50001","correct":"today <- Date().today()","incorrect":"today <- Date.now()","explanation":"EK9 uses factory methods on instances, not static methods. Date().today() creates an unset Date then calls today() on it. Date.now() does not exist. Triggers E50001 — method not resolved. See ek9 -h Date for the full API."},{"error":"E50001","correct":"currentDate <- Date().today()","incorrect":"currentDateXYZ <- Date().today()","explanation":"Renaming the variable means later references to 'currentDate' become unresolved, triggering E50001. See ek9 -h E50001 for details."}],"companions":[]}
{"id":537,"category":"Date, Time, and Duration","question":"How do I format a date for display in EK9?","url":"https://ek9.io/qa/QA0537.html","alternatePhrasings":["How do I convert a date to a string in EK9?","What date format does the $ operator produce?","How do I display dates in different formats?"],"answer":"EK9 provides two approaches: the $ operator for ISO 8601 and Locale methods for human-readable regional formatting.\n\nISO 8601 VIA $ OPERATOR\nThe $ operator on Date, Time, and DateTime always produces ISO 8601 strings:\n  $birthday gives '1971-02-01'\n  $meeting gives '09:30:00'\n  $timestamp gives '2024-06-15T10:30:00Z'\nThis is the machine-readable format, suitable for APIs, logging, and data exchange.\n\nLOCALE FORMATTING — FOUR NAMED LEVELS\nFor human-readable display, use Locale with four named methods:\n  shortFormat: compact, numeric (03/10/2020 or 10/3/20)\n  mediumFormat: abbreviated month (Oct 3, 2020)\n  longFormat: full month name (3. oktobra 2020)\n  fullFormat: includes day-of-week (Saturday, 3 October 2020)\n\nEach level adapts automatically to the locale's conventions.\n\nNO PATTERN STRINGS\nEK9 deliberately avoids format pattern strings like 'yyyy-MM-dd' or 'dd/MM/yyyy'. Pattern strings are error-prone: 'mm' means minutes in Java but months in Excel, 'DD' means day-of-year in Java but day-of-month in Moment.js. Named levels eliminate this entire class of bugs.\n\nSee Q44 for complete Locale formatting guide. See Q549 for locale date formatting examples. See Q551 for RFC 7231 HTTP date headers.","ek9Example":"defines module qa.format.date.display\n\n  defines program\n    FormatDateDisplayDemo()\n      stdout <- Stdout()\n\n      birthday <- 1971-02-01\n      meeting <- 09:30:00\n      timestamp <- 2024-06-15T10:30:00Z\n\n      // $ operator gives ISO 8601\n      stdout.println(`Date ISO: ${birthday}`)\n      stdout.println(`Time ISO: ${meeting}`)\n      stdout.println(`DateTime ISO: ${timestamp}`)\n\n      // Locale formatting with four levels\n      enGB <- Locale(\"en_GB\")\n      enUS <- Locale(\"en_US\")\n      deutsch <- Locale(\"de_DE\")\n\n      // Date formatting\n      stdout.println(`GB short: ${enGB.shortFormat(birthday)}`)\n      stdout.println(`US short: ${enUS.shortFormat(birthday)}`)\n      stdout.println(`GB medium: ${enGB.mediumFormat(birthday)}`)\n      stdout.println(`DE long: ${deutsch.longFormat(birthday)}`)\n      stdout.println(`GB full: ${enGB.fullFormat(birthday)}`)\n      stdout.println(`DE full: ${deutsch.fullFormat(birthday)}`)\n\n      // Time formatting\n      stdout.println(`GB short time: ${enGB.shortFormat(meeting)}`)\n      stdout.println(`DE medium time: ${deutsch.mediumFormat(meeting)}`)\n\n      // DateTime formatting\n      stdout.println(`US short DT: ${enUS.shortFormat(timestamp)}`)\n      stdout.println(`GB medium DT: ${enGB.mediumFormat(timestamp)}`)\n      stdout.println(`DE long DT: ${deutsch.longFormat(timestamp)}`)","migrationContext":"Java: DateTimeFormatter.ofPattern('yyyy-MM-dd') pattern strings, multiple format letter conventions. Python: strftime('%Y-%m-%d') with % codes. JavaScript: toLocaleDateString() or Intl.DateTimeFormat. Go: bizarre reference date '2006-01-02'. Rust: chrono format!() with strftime codes. EK9: $ for ISO 8601, Locale.shortFormat/mediumFormat/longFormat/fullFormat for human-readable. No pattern strings.","keywords":["date","display","duration","format","fullFormat","iso8601","locale","longFormat","mediumFormat","migrate","pattern","shortFormat","string","time","timezone"],"primaryTopics":[],"typicalErrors":[{"error":"E50060","correct":"stdout.println(`GB short: ${enGB.shortFormat(birthday)}`)","incorrect":"stdout.println(`GB short: ${enGB.format(birthday, \"short\")}`)","explanation":"Locale has no generic format() method with a style parameter. EK9 provides four named methods: shortFormat(), mediumFormat(), longFormat(), fullFormat(). See ek9 -h E50060 for details."}],"companions":[]}
{"id":538,"category":"Date, Time, and Duration","question":"How do I parse a date from a string in EK9?","url":"https://ek9.io/qa/QA0538.html","alternatePhrasings":["How do I convert a string to a date in EK9?","How do I handle invalid date strings in EK9?","How does Date construction from strings work?"],"answer":"EK9 uses constructor parsing: pass a string to Date(), Time(), or DateTime() and use guard expressions to handle invalid input safely.\n\nCONSTRUCTOR PARSING\nDate('2024-01-15') parses ISO 8601 date format.\nTime('10:30:00') parses ISO 8601 time format.\nDateTime('2024-06-15T10:30:00Z') parses ISO 8601 datetime format.\n\nINVALID INPUT PRODUCES UNSET\nIf the string is not a valid format, the constructor returns an unset value rather than throwing an exception:\n  badDate <- Date('not-a-date')\n  badDate? is false\nThis follows EK9's tri-state design: invalid parsing produces unset, not an error.\n\nGUARD PATTERN FOR SAFE PARSING\nCombine constructor parsing with guard expressions for clean error handling:\n  if parsed <- Date(userInput)\n    stdout.println(`Valid date: ${parsed}`)\n  else\n    stdout.println('Invalid date format')\nThe guard only enters the if-block when the parsed value is set.\n\nROUND-TRIPPING\nThe $ operator produces a string that can be parsed back:\n  original <- 2024-06-15\n  asString <- $original\n  restored <- Date(asString)\n  require restored == original\n\nSee Q31 for Date and Time basics. See Q552 for Date-to-DateTime promotion. See Q555 for constructing from components.","ek9Example":"defines module qa.parse.date.string\n\n  defines program\n    ParseDateStringDemo()\n      stdout <- Stdout()\n\n      // Parse valid ISO 8601 strings\n      parsedDate <- Date(\"2024-01-15\")\n      parsedTime <- Time(\"10:30:00\")\n      parsedDT <- DateTime(\"2024-06-15T10:30:00Z\")\n\n      stdout.println(`Parsed date: ${parsedDate}`)\n      stdout.println(`Parsed time: ${parsedTime}`)\n      stdout.println(`Parsed datetime: ${parsedDT}`)\n\n      require parsedDate?\n      require parsedTime?\n      require parsedDT?\n\n      // Invalid strings produce unset values\n      badDate <- Date(\"not-a-date\")\n      badTime <- Time(\"invalid\")\n      badDT <- DateTime(\"garbage\")\n\n      require ~badDate?\n      require ~badTime?\n      require ~badDT?\n      stdout.println(`Bad date isSet: ${badDate?}`)\n      stdout.println(`Bad time isSet: ${badTime?}`)\n\n      // Guard pattern for safe parsing\n      userInput <- \"2024-03-20\"\n      if validDate <- Date(userInput)\n        stdout.println(`Valid: ${validDate}`)\n\n      invalidInput <- \"31/02/2024\"\n      if checkedDate <- Date(invalidInput)\n        stdout.println(`This won't print`)\n      else\n        stdout.println(\"Invalid date format\")\n\n      // Round-tripping: $ produces parseable string\n      original <- 2024-06-15\n      asString <- $original\n      restored <- Date(asString)\n      require restored == original\n      stdout.println(`Round-trip: ${original} -> ${asString} -> ${restored}`)\n\n      // Duration parsing also returns unset on invalid\n      validDur <- Duration(\"P1Y6M\")\n      invalidDur <- Duration(\"not-a-duration\")\n      require validDur?\n      require ~invalidDur?\n      stdout.println(`Valid duration: ${validDur}`)","migrationContext":"Java: LocalDate.parse() throws DateTimeParseException on invalid input, requires try-catch. Python: datetime.strptime() throws ValueError, requires try-except. JavaScript: new Date('invalid') returns 'Invalid Date' object (truthy!). Go: time.Parse() returns error. Rust: NaiveDate::parse_from_str() returns Result. EK9: constructor returns unset value on invalid input, guard expression handles it cleanly.","keywords":["constructor","convert","date","duration","guard","invalid","iso8601","isset","null-safe","parse","round-trip","safe","string","time","timezone","unset"],"primaryTopics":["parse date","date from string"],"typicalErrors":[{"error":"E50001","correct":"parsedDate <- Date(\"2024-01-15\")","incorrect":"parsedDateXYZ <- Date(\"2024-01-15\")","explanation":"Renaming the variable means later references to 'parsedDate' become unresolved, triggering E50001. See ek9 -h E50001 for details."},{"error":"E50001","correct":"userInput <- \"2024-03-20\"","incorrect":"userInputXYZ <- \"2024-03-20\"","explanation":"Renaming the variable means later references to 'userInput' become unresolved, triggering E50001. See ek9 -h E50001 for details."}],"companions":[]}
{"id":539,"category":"Date, Time, and Duration","question":"How do I calculate the difference between two dates in EK9?","url":"https://ek9.io/qa/QA0539.html","alternatePhrasings":["How do I find the number of days between two dates?","How does date subtraction work in EK9?","How do I get a Duration from two dates?","How do I do date calculations in EK9?"],"answer":"Subtract one temporal value from another using the - operator. The result is always a Duration.\n\nDATE SUBTRACTION\n  gap <- 2024-12-25 - 2024-01-01\nThe result is a Duration representing the span between the two dates. Use component accessors to extract parts:\n  gap.days() gives the total days\n  gap.hours() gives the total hours\n  gap.years() gives the approximate years (30-day months)\n\nTIME SUBTRACTION\n  workHours <- 17:30 - 09:00\nGives the Duration between two times on the same day.\n\nDATETIME SUBTRACTION\n  elapsed <- laterEvent - earlierEvent\nHandles timezone differences automatically: the result is the actual elapsed Duration regardless of timezone.\n\nNEGATIVE DURATIONS\nIf you subtract a later date from an earlier one, the result is a negative Duration:\n  negative <- 2024-01-01 - 2024-12-25\nNegative durations are valid and useful.\n\nCOMPONENT ACCESSORS\nDuration provides .years(), .months(), .days(), .hours(), .minutes(), .seconds() to extract individual components from the total.\n\nSee Q32 for Duration basics. See Q540 for adding and subtracting time. See Q542 for comparing dates. See Q550 for calendar edge cases.","ek9Example":"defines module qa.date.difference\n\n  defines program\n    DateDifferenceDemo()\n      stdout <- Stdout()\n\n      // Date - Date gives Duration\n      christmas <- 2024-12-25\n      newYear <- 2024-01-01\n      gap <- christmas - newYear\n      stdout.println(`Days until Christmas: ${gap}`)\n      stdout.println(`As days: ${gap.days()}`)\n\n      // Time - Time gives Duration\n      quittingTime <- 17:30\n      startTime <- 09:00\n      workDay <- quittingTime - startTime\n      stdout.println(`Work day: ${workDay}`)\n      stdout.println(`Work hours: ${workDay.hours()}`)\n\n      // DateTime - DateTime gives Duration\n      eventStart <- 2024-06-15T09:00:00Z\n      eventEnd <- 2024-06-15T17:30:00Z\n      eventLength <- eventEnd - eventStart\n      stdout.println(`Event length: ${eventLength}`)\n\n      // Negative duration when subtracting later from earlier\n      backwards <- newYear - christmas\n      stdout.println(`Backwards: ${backwards}`)\n\n      // Component accessors on Duration\n      longSpan <- 2026-06-15 - 2024-01-01\n      stdout.println(`Years: ${longSpan.years()}`)\n      stdout.println(`Months: ${longSpan.months()}`)\n      stdout.println(`Days: ${longSpan.days()}`)\n      stdout.println(`Hours: ${longSpan.hours()}`)\n      stdout.println(`Minutes: ${longSpan.minutes()}`)\n      stdout.println(`Seconds: ${longSpan.seconds()}`)\n\n      // Cross-timezone DateTime subtraction\n      nyMeeting <- 2024-06-15T09:00:00-04:00\n      londonMeeting <- 2024-06-15T14:00:00+01:00\n      tzGap <- londonMeeting - nyMeeting\n      stdout.println(`Cross-timezone gap: ${tzGap}`)","migrationContext":"Java: ChronoUnit.DAYS.between(d1, d2) or Period.between(d1, d2) separate APIs. Python: (date2 - date1).days attribute. JavaScript: manual millisecond subtraction and division. Go: t2.Sub(t1) returns time.Duration. Rust: chrono signed_duration_since(). EK9: simple date2 - date1 gives Duration, component accessors for parts.","keywords":["between","calculate","calculation","components","date","days","difference","duration","elapsed","gap","negative","subtract","time","timezone"],"primaryTopics":[],"typicalErrors":[{"error":"E50060","correct":"gap <- christmas - newYear","incorrect":"gap <- christmas.daysBetween(newYear)","explanation":"Date has no daysBetween() method. Use the - operator to subtract dates and get a Duration, then call .days() on the result for the day count. See ek9 -h E50060 for details."}],"companions":[]}
{"id":540,"category":"Date, Time, and Duration","question":"How do I add or subtract time from a date in EK9?","url":"https://ek9.io/qa/QA0540.html","alternatePhrasings":["How does date arithmetic work with Duration?","How do I add days or months to a date?","What is the duration summing pitfall?"],"answer":"Use the + and - operators with Duration literals. EK9 supports both simple and compound assignment forms.\n\nBASIC ARITHMETIC\n  nextWeek <- 2024-01-15 + P7D\n  lastMonth <- 2024-06-15 - P1M\n  meetingEnd <- 2024-06-15T10:00:00Z + PT1H30M\n\nCOMPOUND ASSIGNMENT\n  appointment <- 2024-03-01\n  appointment += P2W\n  appointment -= P3D\n\nINCREMENT AND DECREMENT\n  today <- Date().today()\n  today++\n  today--\nFor Date, ++ adds one day and -- subtracts one day.\nFor DateTime, ++ and -- also add/subtract one day.\n\nTHE DURATION SUMMING PITFALL\nIn languages with true calendar month arithmetic (Java, Python), month addition is NOT associative:\n  Java: Jan 31 + 1 month = Feb 28 (clamped to month end)\n        Feb 28 + 1 month = Mar 28\n  But:  Jan 31 + 2 months = Mar 31 (different!)\nSequential single-month additions clamp at each step, losing information.\n\nEK9 uses a predictable 30-day approximation for months:\n  P1M = 30 days. P2M = 60 days. Always.\n  date + P1M + P1M = date + 60 days\n  date + P2M = date + 60 days (same result!)\nMonth arithmetic IS associative in EK9. The trade-off: P1M is always 30 days, not the actual number of days in any specific month.\n\nFor calendar-precise month logic, construct new dates from components (see Q550 for the deep dive).\n\nMILLISECOND ARITHMETIC\nYou can also add Millisecond values:\n  precise <- 2024-06-15T10:00:00Z + 5000ms\n\nSee Q32 for Duration literals. See Q539 for date subtraction. See Q550 for calendar edge cases and the full duration summing deep dive. See Q556 for Time wrapping.","ek9Example":"defines module qa.add.subtract.time\n\n  defines program\n    AddSubtractTimeDemo()\n      stdout <- Stdout()\n\n      // Basic date + duration\n      start <- 2024-01-15\n      nextWeek <- start + P7D\n      lastMonth <- start - P1M\n      stdout.println(`Start: ${start}`)\n      stdout.println(`+ P7D: ${nextWeek}`)\n      stdout.println(`- P1M: ${lastMonth}`)\n\n      // Complex duration\n      future <- start + P1Y6M10D\n      stdout.println(`+ P1Y6M10D: ${future}`)\n\n      // Compound assignment\n      appointment <- 2024-03-01\n      appointment += P2W\n      stdout.println(`+ P2W: ${appointment}`)\n      appointment -= P3D\n      stdout.println(`- P3D: ${appointment}`)\n\n      // Increment and decrement (one day)\n      mutableDate <- 2024-06-15\n      mutableDate++\n      stdout.println(`After ++: ${mutableDate}`)\n      mutableDate--\n      stdout.println(`After --: ${mutableDate}`)\n\n      // DateTime arithmetic\n      meeting <- 2024-06-15T10:00:00Z\n      meetingEnd <- meeting + PT1H30M\n      stdout.println(`Meeting end: ${meetingEnd}`)\n\n      // Duration summing: EK9 is associative\n      // P1M = 30 days always, so P1M + P1M = P2M = 60 days\n      base <- 2024-01-15\n      sequential <- base + P1M + P1M\n      combined <- base + P2M\n      stdout.println(`Sequential P1M + P1M: ${sequential}`)\n      stdout.println(`Combined P2M: ${combined}`)\n      require sequential == combined\n\n      // Millisecond arithmetic\n      precise <- 2024-06-15T10:00:00Z + 5000ms\n      stdout.println(`+ 5000ms: ${precise}`)\n\n      // Time arithmetic (wraps at 24h - see Q556)\n      evening <- 22:00\n      lateNight <- evening + PT3H\n      stdout.println(`22:00 + PT3H: ${lateNight}`)","migrationContext":"Java: date.plusDays(7), date.plusMonths(1) calendar-safe but non-associative (Jan 31 + 1 month + 1 month differs from Jan 31 + 2 months). Python: timedelta(days=7) for days only, relativedelta for months (same non-associativity). JavaScript: manual setDate/setMonth (notoriously broken). Go: t.AddDate(0, 1, 0) calendar-safe but non-associative. EK9: date + P7D, P1M = 30 days always, associative arithmetic. Trade-off: predictable vs calendar-precise.","keywords":["add","arithmetic","associative","calculate","calendar","date","decrement","duration","increment","migrate","minus","month","pitfall","plus","subtract","summing","time","timezone"],"primaryTopics":[],"typicalErrors":[{"error":"E50060","correct":"nextWeek <- start + P7D","incorrect":"nextWeek <- start.plusDays(7)","explanation":"Date has no plusDays() method. EK9 uses the + operator with Duration literals (P7D for 7 days) for date arithmetic. See ek9 -h E50060 for details."}],"companions":[]}
{"id":541,"category":"Date, Time, and Duration","question":"How do I handle timezones in EK9?","url":"https://ek9.io/qa/QA0541.html","alternatePhrasings":["How does EK9 handle timezone-aware dates?","How do I create a DateTime with a specific timezone?","What timezone formats does EK9 support?"],"answer":"DateTime is EK9's timezone-aware type. It always carries a timezone. Use withSameInstant() to convert between timezones for display, and withZone() to reinterpret the clock reading in a different zone.\n\nTIMEZONE IN LITERALS\nUTC: 2024-06-15T10:30:00Z (the Z suffix means UTC)\nOffset: 2024-06-15T10:30:00-05:00 (five hours behind UTC)\nOffset: 2024-06-15T10:30:00+05:30 (India Standard Time)\n\nTIMEZONE ACCESSORS\nzone() returns the timezone string: 'Z', '-05:00', '+05:30'\noffSetFromUTC() returns the offset as a Duration: PT0S for UTC, PT-5H for -05:00\n\nCONVERTING TIMEZONES\nwithSameInstant(zoneId) converts the DISPLAY to another timezone while preserving the actual moment in time. The clock reading changes but the instant stays the same:\n  utcMeeting <- 2024-06-15T10:30:00Z\n  nyTime <- utcMeeting.withSameInstant('America/New_York')\n  nyTime is 2024-06-15T06:30:00-04:00 (same instant, different clock)\n\nwithZone(zoneId) changes the timezone label WITHOUT converting the time. The clock reading stays but the instant changes:\n  labeled <- utcMeeting.withZone('America/New_York')\n  labeled is 2024-06-15T10:30:00-04:00 (same clock, different instant)\n\nSee Q546 for a detailed comparison of withSameInstant vs withZone.\n\nIANA TIMEZONE NAMES\nEK9 supports standard IANA timezone names:\n  'America/New_York', 'Europe/London', 'Asia/Tokyo', 'Australia/Sydney'\nAlso supports UTC offsets: 'UTC', 'UTC+5', 'UTC-8'\n\nSee Q545 for timezone conversion patterns. See Q547 for UTC storage best practices. See Q548 for comparing across timezones. See Q553 for common timezone mistakes.","ek9Example":"defines module qa.handle.timezones\n\n  defines program\n    HandleTimezonesDemo()\n      stdout <- Stdout()\n\n      // UTC literal\n      utcMeeting <- 2024-06-15T10:30:00Z\n      stdout.println(`UTC: ${utcMeeting}`)\n      stdout.println(`Zone: ${utcMeeting.zone()}`)\n      stdout.println(`Offset: ${utcMeeting.offSetFromUTC()}`)\n\n      // Offset literals\n      estEvent <- 2024-06-15T10:30:00-05:00\n      indiaEvent <- 2024-06-15T10:30:00+05:30\n      stdout.println(`EST: ${estEvent}, zone: ${estEvent.zone()}`)\n      stdout.println(`IST: ${indiaEvent}, zone: ${indiaEvent.zone()}`)\n\n      // withSameInstant: same moment, different clock\n      nyTime <- utcMeeting.withSameInstant(\"America/New_York\")\n      tokyoTime <- utcMeeting.withSameInstant(\"Asia/Tokyo\")\n      londonTime <- utcMeeting.withSameInstant(\"Europe/London\")\n      stdout.println(`NY: ${nyTime}`)\n      stdout.println(`Tokyo: ${tokyoTime}`)\n      stdout.println(`London: ${londonTime}`)\n\n      // withZone: same clock, different timezone label\n      relabeled <- utcMeeting.withZone(\"America/New_York\")\n      stdout.println(`Relabeled: ${relabeled}`)\n\n      // The key difference:\n      // withSameInstant changes the clock to show the same moment\n      // withZone keeps the clock and changes the timezone\n      // So nyTime and relabeled show different times!\n      stdout.println(`Same instant NY hour: ${nyTime.hour()}`)\n      stdout.println(`Relabeled NY hour: ${relabeled.hour()}`)\n\n      // Offset accessor returns Duration\n      estOffset <- estEvent.offSetFromUTC()\n      stdout.println(`EST offset: ${estOffset}`)","migrationContext":"Java: ZonedDateTime with ZoneId, withZoneSameInstant() and withZoneSameLocal() (confusing names). Python: datetime with pytz/zoneinfo, astimezone() for conversion. JavaScript: no built-in timezone support, Intl.DateTimeFormat or moment-timezone. Go: time.In(location) for conversion. Rust: chrono with_timezone(). EK9: DateTime built-in with zone(), withSameInstant() and withZone(), IANA names supported.","keywords":["convert","datetime","duration","iana","offset","time","timezone","utc","withSameInstant","withZone","zone"],"primaryTopics":["timezone","time zone"],"typicalErrors":[{"error":"E50060","correct":"nyTime <- utcMeeting.withSameInstant(\"America/New_York\")","incorrect":"nyTime <- utcMeeting.toTimezone(\"America/New_York\")","explanation":"DateTime has no toTimezone() method. Use withSameInstant() to convert a DateTime to another timezone while preserving the same instant. See ek9 -h E50060 for details."}],"companions":[]}
{"id":542,"category":"Date, Time, and Duration","question":"How do I compare dates in EK9?","url":"https://ek9.io/qa/QA0542.html","alternatePhrasings":["How do I check if one date is before another?","How does date sorting work in EK9?","What comparison operators work on dates?"],"answer":"All temporal types (Date, Time, DateTime, Duration, Millisecond) support the full set of comparison operators.\n\nCOMPARISON OPERATORS\n  == equal\n  <> not equal\n  < less than (earlier)\n  > greater than (later)\n  <= less than or equal\n  >= greater than or equal\n  <=> spaceship (returns -1, 0, or 1)\n  <~> fuzzy comparison\n\nDATE COMPARISON\nDates compare chronologically:\n  2024-01-01 < 2024-12-25 is true\n  2024-06-15 == 2024-06-15 is true\n\nTIME COMPARISON\nTimes compare within the day:\n  09:00 < 17:00 is true\n\nDATETIME COMPARISON — UTC NORMALIZED\nDateTimes in different timezones compare by their UTC-equivalent instant:\n  2024-06-15T10:00:00Z == 2024-06-15T06:00:00-04:00 is true\n  (Both represent the same moment)\nThis means you can safely compare timestamps from different timezones.\n\nSPACESHIP OPERATOR\nThe <=> operator returns an Integer: negative (before), zero (equal), positive (after). Useful for custom sorting logic.\n\nFUZZY COMPARISON\nThe <~> operator returns an Integer representing approximate distance. For dates this is the absolute difference in days.\n\nSORTING\nSince all comparison operators are defined, temporal values work naturally in sorted collections and stream sort operations.\n\nSee Q31 for Date and Time basics. See Q548 for cross-timezone comparison details.","ek9Example":"defines module qa.compare.dates\n\n  defines program\n    CompareDatesDemo()\n      stdout <- Stdout()\n\n      // Date comparison\n      newYear <- 2024-01-01\n      christmas <- 2024-12-25\n      summer <- 2024-06-15\n      sameSummer <- 2024-06-15\n\n      require newYear < christmas\n      require christmas > newYear\n      require summer == sameSummer\n      require newYear <> christmas\n      require newYear <= summer\n      require christmas >= summer\n      stdout.println(`Jan < Dec: ${newYear < christmas}`)\n      stdout.println(`Jun == Jun: ${summer == sameSummer}`)\n\n      // Time comparison\n      morning <- 09:00\n      evening <- 17:00\n      require morning < evening\n      stdout.println(`09:00 < 17:00: ${morning < evening}`)\n\n      // DateTime comparison — UTC normalized\n      utcNoon <- 2024-06-15T12:00:00Z\n      nyMorning <- 2024-06-15T08:00:00-04:00\n      // Both represent the same instant (12:00 UTC = 08:00 EDT)\n      require utcNoon == nyMorning\n      stdout.println(`UTC noon == NY 8am: ${utcNoon == nyMorning}`)\n\n      // Different instants in different zones\n      laterNY <- 2024-06-15T14:00:00-04:00\n      require laterNY > utcNoon\n      stdout.println(`NY 2pm > UTC noon: ${laterNY > utcNoon}`)\n\n      // Spaceship operator\n      ordering <- newYear <=> christmas\n      stdout.println(`Jan <=> Dec: ${ordering}`)\n      require ordering < 0\n\n      // Fuzzy comparison\n      fuzzy <- newYear <~> christmas\n      stdout.println(`Fuzzy distance: ${fuzzy}`)\n\n      // Duration comparison\n      shortDur <- PT30M\n      longDur <- PT2H\n      require shortDur < longDur\n      stdout.println(`30min < 2h: ${shortDur < longDur}`)\n\n      // Millisecond comparison\n      shortMs <- 100ms\n      longMs <- 500ms\n      require shortMs < longMs\n      require longMs > shortMs\n      stdout.println(`100ms < 500ms: ${shortMs < longMs}`)","migrationContext":"Java: compareTo(), isBefore(), isAfter(), isEqual() methods. Python: direct comparison operators work on datetime objects. JavaScript: compare via getTime() milliseconds (operators on Date objects are unreliable). Go: t.Before(), t.After(), t.Equal() methods. Rust: PartialOrd trait. EK9: full operator set (==, <>, <, >, <=, >=, <=>, <~>) on all temporal types, UTC-normalized DateTime comparison.","keywords":["after","before","chronological","compare","date","datetime","duration","equal","fuzzy","operators","sort","spaceship","time","timezone"],"primaryTopics":[],"typicalErrors":[{"error":"E50060","correct":"require newYear < christmas","incorrect":"require newYear.isBefore(christmas)","explanation":"Date has no isBefore() method. EK9 uses comparison operators (<, >, <=, >=, ==, <>) directly on temporal types. See ek9 -h E50060 for details."}],"companions":[]}
{"id":543,"category":"Date, Time, and Duration","question":"How do I work with Unix timestamps in EK9?","url":"https://ek9.io/qa/QA0543.html","alternatePhrasings":["How do I convert epoch milliseconds to a date?","How do I get the Unix epoch time in EK9?","How does Millisecond relate to epoch time?","How do I work with epoch time in EK9?"],"answer":"EK9 uses the Millisecond type for epoch-based timestamps. SystemClock().millisecond() returns the current Unix epoch time in milliseconds.\n\nGETTING EPOCH TIME\n  epochNow <- SystemClock().millisecond()\nThis returns a Millisecond value representing milliseconds since 1970-01-01T00:00:00Z (the Unix epoch).\n\nDATE FROM EPOCH DAYS\nThe Date constructor accepts days from epoch:\n  Date(daysFromEpoch) creates a date from the number of days since 1970-01-01.\nThe #< operator extracts epoch days from a Date:\n  epochDays <- #< myDate\n\nDATETIME AND EPOCH\nDateTime does not have a direct epoch constructor, but you can work with epoch values through Duration and Millisecond arithmetic:\n  epochMs <- SystemClock().millisecond()\n  asDuration <- epochMs.duration()\n\nMILLISECOND AS TIMESTAMP\nMillisecond is the natural type for Unix timestamps, API timestamps, and database epoch values. It carries type information (this is milliseconds, not nanoseconds or seconds) and supports arithmetic.\n\nSee Q41 for Millisecond basics. See Q544 for benchmarking with Millisecond. See Q536 for getting current date and time.","ek9Example":"defines module qa.unix.timestamps\n\n  defines program\n    UnixTimestampsDemo()\n      stdout <- Stdout()\n\n      // Current epoch time in milliseconds\n      epochNow <- SystemClock().millisecond()\n      stdout.println(`Epoch now: ${epochNow}`)\n\n      // Date from epoch days\n      // 1970-01-01 is day 0\n      unixEpoch <- Date(0)\n      stdout.println(`Epoch date: ${unixEpoch}`)\n\n      // Extract epoch days from a date\n      today <- Date().today()\n      epochDays <- #< today\n      stdout.println(`Today's epoch days: ${epochDays}`)\n\n      // Round-trip: epoch days back to date\n      restored <- Date(epochDays)\n      require restored == today\n      stdout.println(`Restored: ${restored}`)\n\n      // Millisecond to Duration conversion\n      fiveSeconds <- 5000ms\n      asDuration <- fiveSeconds.duration()\n      stdout.println(`5000ms as Duration: ${asDuration}`)\n\n      // Millisecond arithmetic for timestamp differences\n      earlier <- SystemClock().millisecond()\n      stdout.println(\"Some work here\")\n      later <- SystemClock().millisecond()\n      elapsed <- later - earlier\n      stdout.println(`Elapsed: ${elapsed}`)\n\n      // Known epoch dates\n      y2k <- Date(10957)\n      stdout.println(`Y2K (day 10957): ${y2k}`)","migrationContext":"Java: System.currentTimeMillis() returns raw long, Instant.toEpochMilli(), LocalDate.toEpochDay(). Python: time.time() returns float seconds, datetime.timestamp(). JavaScript: Date.now() returns milliseconds, new Date(epochMs). Go: time.Now().UnixMilli(). Rust: SystemTime::now().duration_since(UNIX_EPOCH). EK9: SystemClock().millisecond() returns typed Millisecond, Date(epochDays) for date construction, #< for epoch extraction.","keywords":["convert","days","duration","epoch","millisecond","seconds","systemclock","time","timestamp","timezone","unix"],"primaryTopics":[],"typicalErrors":[{"error":"E50060","correct":"epochNow <- SystemClock().millisecond()","incorrect":"epochNow <- SystemClock().currentTimeMillis()","explanation":"SystemClock has no currentTimeMillis() method. Use millisecond() to get the current epoch time as a typed Millisecond value. See ek9 -h E50060 for details."}],"companions":[]}
{"id":544,"category":"Date, Time, and Duration","question":"How do I measure elapsed time for benchmarking in EK9?","url":"https://ek9.io/qa/QA0544.html","alternatePhrasings":["How do I time how long something takes in EK9?","How do I use SystemClock for performance measurement?","How do I profile code execution time in EK9?"],"answer":"Use SystemClock().millisecond() before and after the code section, then subtract to get elapsed Millisecond.\n\nBASIC TIMING PATTERN\n  startMs <- SystemClock().millisecond()\n  // ... code to measure ...\n  endMs <- SystemClock().millisecond()\n  elapsed <- endMs - startMs\n  stdout.println(`Elapsed: ${elapsed}`)\n\nThe result is a typed Millisecond value, not a raw integer. You can convert it to Duration with .duration() or get the raw value.\n\nBUILT-IN PROFILING FLAGS\nEK9 also has compiler-level profiling:\n  ek9 -P program.ek9 runs with profiling enabled\n  ek9 -Pf program.ek9 profiles with full detail\nThe compiler instruments the code automatically. For micro-benchmarks, manual SystemClock timing gives you control over exactly what is measured.\n\nMILLISECOND ARITHMETIC\nYou can accumulate timing results:\n  totalMs <- 0ms\n  totalMs += elapsed\nOr compute averages:\n  averageMs <- totalMs / iterations\n\nCONVERT TO DURATION\n  elapsed.duration() converts to Duration for display in hours/minutes/seconds format.\n\nSee Q41 for Millisecond type details. See Q543 for Unix timestamps. See Q322 for the profiling system. See Q631 for benchmarking two approaches.","ek9Example":"defines module qa.elapsed.time.benchmarking\n\n  defines program\n    ElapsedTimeBenchmarkingDemo()\n      stdout <- Stdout()\n\n      // Basic timing pattern\n      startMs <- SystemClock().millisecond()\n\n      // Simulate work with a loop\n      counter <- 0\n      for i in 1 ... 1000\n        counter += i\n\n      endMs <- SystemClock().millisecond()\n      elapsed <- endMs - startMs\n      stdout.println(`Loop elapsed: ${elapsed}`)\n      stdout.println(`Counter: ${counter}`)\n\n      // Convert to Duration\n      asDuration <- elapsed.duration()\n      stdout.println(`As Duration: ${asDuration}`)\n\n      // Accumulate multiple measurements\n      totalMs <- 0ms\n      for run in 1 ... 3\n        runStart <- SystemClock().millisecond()\n        sum <- 0\n        for j in 1 ... 500\n          sum += j\n        runEnd <- SystemClock().millisecond()\n        runElapsed <- runEnd - runStart\n        totalMs += runElapsed\n        stdout.println(`Run ${run}: ${runElapsed}`)\n\n      stdout.println(`Total: ${totalMs}`)\n\n      // Millisecond comparison for thresholds\n      threshold <- 1000ms\n      if totalMs < threshold\n        stdout.println(\"Fast enough\")\n      else\n        stdout.println(\"Too slow\")","migrationContext":"Java: System.nanoTime() for benchmarking (not currentTimeMillis which has clock adjustment issues). Python: time.perf_counter() or timeit module. JavaScript: performance.now() for high-resolution timing. Go: time.Now() with time.Since(). Rust: std::time::Instant::now() with elapsed(). EK9: SystemClock().millisecond() with typed subtraction, built-in -P profiling flag.","keywords":["benchmark","date","duration","elapsed","measure","millisecond","performance","profile","systemclock","time","timezone","timing"],"primaryTopics":[],"typicalErrors":[{"error":"E50001","correct":"elapsed <- endMs - startMs","incorrect":"elapsedXYZ <- endMs - startMs","explanation":"Renaming the variable means later references to 'elapsed' become unresolved, triggering E50001. See ek9 -h E50001 for details."},{"error":"E50001","correct":"startMs <- SystemClock().millisecond()","incorrect":"startMs <- SystemClock.currentTimeMillis()","explanation":"EK9 uses SystemClock().millisecond(), not static Java-style methods. SystemClock is instantiated then millisecond() is called on it. Triggers E50001 — method not resolved. See ek9 -h SystemClock for the full API."}],"companions":[]}
{"id":545,"category":"Date, Time, and Duration","question":"How do I convert a DateTime to a different timezone in EK9?","url":"https://ek9.io/qa/QA0545.html","alternatePhrasings":["How do I display a time in a different timezone?","How do I convert UTC to local time in EK9?","How does withSameInstant work for timezone conversion?"],"answer":"Use withSameInstant(zoneId) to convert a DateTime to show the same moment in a different timezone. This is the most common timezone operation.\n\nCONVERTING FOR DISPLAY\n  utcEvent <- 2024-06-15T14:00:00Z\n  nyDisplay <- utcEvent.withSameInstant('America/New_York')\n  tokyoDisplay <- utcEvent.withSameInstant('Asia/Tokyo')\n  londonDisplay <- utcEvent.withSameInstant('Europe/London')\nAll three represent the SAME moment. Only the clock reading and timezone label change.\n\nCOMMON IANA ZONE NAMES\n  Americas: America/New_York, America/Chicago, America/Denver, America/Los_Angeles, America/Sao_Paulo\n  Europe: Europe/London, Europe/Paris, Europe/Berlin, Europe/Moscow\n  Asia: Asia/Tokyo, Asia/Shanghai, Asia/Kolkata, Asia/Dubai\n  Pacific: Australia/Sydney, Pacific/Auckland\n  UTC: UTC, Etc/UTC\n\nPATTERN: STORE UTC, DISPLAY LOCAL\nStore all timestamps in UTC. Convert to the user's timezone only for display:\n  stored <- 2024-06-15T14:00:00Z\n  userZone <- 'America/New_York'\n  displayed <- stored.withSameInstant(userZone)\nThis avoids timezone confusion in comparisons and arithmetic.\n\nSee Q541 for timezone basics. See Q546 for withSameInstant vs withZone. See Q547 for UTC storage best practices. See Q548 for cross-timezone comparison.","ek9Example":"defines module qa.convert.timezone\n\n  defines program\n    ConvertTimezoneDemo()\n      stdout <- Stdout()\n\n      // UTC event\n      utcEvent <- 2024-06-15T14:00:00Z\n      stdout.println(`UTC: ${utcEvent}`)\n\n      // Convert to various timezones\n      nyTime <- utcEvent.withSameInstant(\"America/New_York\")\n      chicagoTime <- utcEvent.withSameInstant(\"America/Chicago\")\n      laTime <- utcEvent.withSameInstant(\"America/Los_Angeles\")\n      londonTime <- utcEvent.withSameInstant(\"Europe/London\")\n      parisTime <- utcEvent.withSameInstant(\"Europe/Paris\")\n      tokyoTime <- utcEvent.withSameInstant(\"Asia/Tokyo\")\n      sydneyTime <- utcEvent.withSameInstant(\"Australia/Sydney\")\n\n      stdout.println(`New York: ${nyTime}`)\n      stdout.println(`Chicago: ${chicagoTime}`)\n      stdout.println(`Los Angeles: ${laTime}`)\n      stdout.println(`London: ${londonTime}`)\n      stdout.println(`Paris: ${parisTime}`)\n      stdout.println(`Tokyo: ${tokyoTime}`)\n      stdout.println(`Sydney: ${sydneyTime}`)\n\n      // All are the same instant\n      require nyTime == utcEvent\n      require tokyoTime == utcEvent\n      require sydneyTime == utcEvent\n      stdout.println(`All equal UTC: ${nyTime == utcEvent and tokyoTime == utcEvent}`)\n\n      // Store UTC, display local pattern\n      stored <- 2024-12-25T00:00:00Z\n      userZone <- \"Europe/Berlin\"\n      displayed <- stored.withSameInstant(userZone)\n      stdout.println(`Stored: ${stored}`)\n      stdout.println(`User sees: ${displayed}`)","migrationContext":"Java: dateTime.withZoneSameInstant(ZoneId.of('America/New_York')). Python: dt.astimezone(zoneinfo.ZoneInfo('America/New_York')). JavaScript: no built-in, Intl.DateTimeFormat for display only. Go: t.In(loc). Rust: chrono with_timezone(). EK9: dateTime.withSameInstant('America/New_York') clear and concise.","keywords":["convert","display","duration","iana","local","time","timezone","utc","withSameInstant","zone"],"primaryTopics":[],"typicalErrors":[{"error":"E50060","correct":"nyTime <- utcEvent.withSameInstant(\"America/New_York\")","incorrect":"nyTime <- utcEvent.convertTo(\"America/New_York\")","explanation":"DateTime has no convertTo() method. Use withSameInstant() to convert a DateTime to show the same moment in a different timezone. See ek9 -h E50060 for details."}],"companions":[]}
{"id":546,"category":"Date, Time, and Duration","question":"What is the difference between withSameInstant() and withZone() in EK9?","url":"https://ek9.io/qa/QA0546.html","alternatePhrasings":["When should I use withSameInstant vs withZone?","How do withSameInstant and withZone differ?","Why does EK9 have two timezone methods?"],"answer":"These two methods serve fundamentally different purposes. Confusing them is one of the most common timezone bugs.\n\nwithSameInstant(zoneId): SAME MOMENT, DIFFERENT CLOCK\nConverts the display to show the same physical instant in a different timezone. The moment in time stays the same, but the clock reading changes:\n  10:00 UTC .withSameInstant('America/New_York') = 06:00 EDT\n  Same moment: both represent the same point on the timeline.\n  Use case: 'What time is it in New York RIGHT NOW?'\n\nwithZone(zoneId): SAME CLOCK, DIFFERENT MOMENT\nKeeps the clock reading (hour, minute, second) but changes the timezone label. This creates a DIFFERENT moment in time:\n  10:00 UTC .withZone('America/New_York') = 10:00 EDT\n  Different moment: 10:00 UTC and 10:00 EDT are 4 hours apart.\n  Use case: 'The meeting is at 10:00 in the New York office' (when you have the local time as UTC by mistake).\n\nTHE CRITICAL DIFFERENCE\nwithSameInstant: preserves the INSTANT, changes the DISPLAY\nwithZone: preserves the DISPLAY, changes the INSTANT\n\nMost timezone work uses withSameInstant. Use withZone only when you know the clock reading is correct but the timezone label is wrong (e.g., importing data where times were recorded in local time but stored as UTC).\n\nSee Q541 for timezone basics. See Q545 for timezone conversion patterns. See Q547 for UTC storage practices. See Q553 for common timezone mistakes.","ek9Example":"defines module qa.withsameinstant.vs.withzone\n\n  defines constant\n    newYorkZone <- \"America/New_York\"\n\n  defines program\n    WithSameInstantVsWithZoneDemo()\n      stdout <- Stdout()\n\n      utcTime <- 2024-06-15T10:00:00Z\n      stdout.println(`Original UTC: ${utcTime}`)\n\n      // withSameInstant: same moment, different clock\n      nyInstant <- utcTime.withSameInstant(newYorkZone)\n      stdout.println(`withSameInstant NY: ${nyInstant}`)\n      stdout.println(`  Hour: ${nyInstant.hour()}`)\n      // The hour changes (10:00 UTC -> 06:00 EDT)\n      // But they represent the SAME moment\n      require utcTime == nyInstant\n\n      // withZone: same clock, different moment\n      nyZone <- utcTime.withZone(newYorkZone)\n      stdout.println(`withZone NY: ${nyZone}`)\n      stdout.println(`  Hour: ${nyZone.hour()}`)\n      // The hour stays 10 but the timezone changes\n      // They represent DIFFERENT moments (4 hours apart)\n      require utcTime <> nyZone\n\n      // Visual comparison\n      stdout.println(\"---\")\n      stdout.println(`UTC:             ${utcTime}`)\n      stdout.println(`Same instant NY: ${nyInstant}`)\n      stdout.println(`Same clock NY:   ${nyZone}`)\n\n      // The difference between the two results\n      instantDiff <- nyZone - nyInstant\n      stdout.println(`Difference: ${instantDiff}`)\n\n      // Real-world example: Tokyo office meeting at 15:00 local\n      tokyoMeeting <- 2024-06-15T15:00:00+09:00\n      // What time should NY team join? (withSameInstant)\n      nyJoinTime <- tokyoMeeting.withSameInstant(newYorkZone)\n      stdout.println(`Tokyo 15:00 = NY ${nyJoinTime.hour()}:00`)","migrationContext":"Java: withZoneSameInstant() vs withZoneSameLocal() (confusing names, easy to mix up). Python: astimezone() for same-instant, replace(tzinfo=) for same-clock (dangerous). JavaScript: no built-in distinction. Go: In() for same-instant only. Rust: chrono with_timezone() for same-instant. EK9: withSameInstant() and withZone() with clear descriptive names.","keywords":["clock","convert","display","duration","instant","moment","time","timezone","withSameInstant","withZone"],"primaryTopics":[],"typicalErrors":[{"error":"E50060","correct":"nyInstant <- utcTime.withSameInstant(newYorkZone)","incorrect":"nyInstant <- utcTime.withZoneSameInstant(newYorkZone)","explanation":"DateTime has no withZoneSameInstant() method (that is the Java name). EK9 uses the clearer name withSameInstant(). See ek9 -h E50060 for details."},{"error":"E50001","correct":"nyInstant <- utcTime.withSameInstant(newYorkZone)","incorrect":"nyInstantXYZ <- utcTime.withSameInstant(newYorkZone)","explanation":"Renaming the variable means later references to 'nyInstant' become unresolved, triggering E50001. See ek9 -h E50001 for details."}],"companions":[]}
{"id":547,"category":"Date, Time, and Duration","question":"How should I store dates in EK9 — UTC or local time?","url":"https://ek9.io/qa/QA0547.html","alternatePhrasings":["Should I store timestamps in UTC?","What is the best practice for storing dates?","How do I handle the store UTC display local pattern?"],"answer":"Always store in UTC. Convert to local time only for display. This is the universal best practice and EK9 makes it natural.\n\nWHY UTC FOR STORAGE\n1. Unambiguous: UTC has no daylight saving transitions. 14:00 UTC is always 14:00 UTC.\n2. Comparable: two UTC timestamps can be directly compared without timezone conversion.\n3. Arithmetic: adding PT1H to a UTC time always moves forward exactly one hour. With local times, adding an hour across a DST boundary can give unexpected results.\n4. Portable: UTC means the same thing everywhere. 'EST' could be US Eastern or Australian Eastern.\n\nTHE PATTERN\nStore: always as UTC DateTime literals or UTC-constructed values.\n  created <- 2024-06-15T14:00:00Z\n  updated <- DateTime().now()\n\nDisplay: convert to user's timezone with withSameInstant:\n  userZone <- 'America/New_York'\n  displayTime <- stored.withSameInstant(userZone)\n\nFormat: use Locale for regional formatting:\n  enUS <- Locale('en_US')\n  formatted <- enUS.longFormat(displayTime)\n\nFULL PIPELINE\n  stored <- 2024-06-15T14:00:00Z\n  local <- stored.withSameInstant('Europe/Berlin')\n  deDE <- Locale('de_DE')\n  stdout.println(deDE.longFormat(local))\nStore (UTC) to display (timezone) to format (locale) is the three-step pipeline.\n\nSee Q541 for timezone basics. See Q545 for timezone conversion. See Q546 for withSameInstant vs withZone. See Q549 for locale formatting. See Q553 for common timezone mistakes.","ek9Example":"defines module qa.store.utc.display.local\n\n  defines program\n    StoreUtcDisplayLocalDemo()\n      stdout <- Stdout()\n\n      // STORE: always in UTC\n      orderCreated <- 2024-06-15T14:30:00Z\n      orderShipped <- 2024-06-16T09:15:00Z\n      stdout.println(`Stored (UTC): ${orderCreated}`)\n\n      // COMPARE: UTC timestamps compare correctly\n      require orderShipped > orderCreated\n      elapsed <- orderShipped - orderCreated\n      stdout.println(`Time to ship: ${elapsed}`)\n\n      // DISPLAY: convert to user's timezone\n      userZone <- \"America/New_York\"\n      localCreated <- orderCreated.withSameInstant(userZone)\n      localShipped <- orderShipped.withSameInstant(userZone)\n      stdout.println(`User sees created: ${localCreated}`)\n      stdout.println(`User sees shipped: ${localShipped}`)\n\n      // FORMAT: locale-aware display\n      enUS <- Locale(\"en_US\")\n      stdout.println(`Formatted: ${enUS.longFormat(localCreated)}`)\n\n      // Full pipeline: store -> timezone -> locale\n      deDE <- Locale(\"de_DE\")\n      berlinTime <- orderCreated.withSameInstant(\"Europe/Berlin\")\n      stdout.println(`German user sees: ${deDE.longFormat(berlinTime)}`)\n\n      // Multiple users see different times for the same event\n      tokyoTime <- orderCreated.withSameInstant(\"Asia/Tokyo\")\n      jaJP <- Locale(\"ja_JP\")\n      stdout.println(`Tokyo user sees: ${jaJP.longFormat(tokyoTime)}`)\n\n      // All stored values stay UTC for correct comparison\n      require localCreated == orderCreated\n      require berlinTime == orderCreated\n      require tokyoTime == orderCreated","migrationContext":"All languages: the 'store UTC, display local' pattern is universal best practice. Java: Instant for storage, ZonedDateTime for display. Python: datetime.utcnow() (deprecated) or datetime.now(UTC). JavaScript: Date internally stores UTC, toLocaleString() for display. Go: time.UTC for storage, In(loc) for display. EK9: DateTime literal with Z suffix for storage, withSameInstant() for display, Locale for formatting.","keywords":["best practice","display","duration","local","locale","pattern","store","time","timezone","utc"],"primaryTopics":[],"typicalErrors":[{"error":"E50060","correct":"localCreated <- orderCreated.withSameInstant(userZone)","incorrect":"localCreated <- orderCreated.toLocalTime(userZone)","explanation":"DateTime has no toLocalTime() method. Use withSameInstant() to convert UTC timestamps to a local timezone for display. See ek9 -h E50060 for details."}],"companions":[]}
{"id":548,"category":"Date, Time, and Duration","question":"How do I compare DateTimes across different timezones in EK9?","url":"https://ek9.io/qa/QA0548.html","alternatePhrasings":["Do timezone differences affect DateTime comparison?","How does EK9 handle comparing UTC and local times?","Are DateTimes normalized before comparison?"],"answer":"EK9 automatically normalizes DateTimes to UTC for comparison. Two DateTimes representing the same instant are always equal, regardless of their timezone labels.\n\nAUTOMATIC UTC NORMALIZATION\n  utcNoon <- 2024-06-15T12:00:00Z\n  nyMorning <- 2024-06-15T08:00:00-04:00\n  require utcNoon == nyMorning\nBoth represent 12:00 UTC. The comparison sees through the timezone difference.\n\nALL COMPARISON OPERATORS NORMALIZE\nThis works for ==, <>, <, >, <=, >=, <=>:\n  require 2024-06-15T12:00:00Z < 2024-06-15T09:00:00-04:00\n  (12:00 UTC < 13:00 UTC)\n\nSORTING WORKS CORRECTLY\nA collection of DateTimes from different timezones sorts by their actual chronological order, not by clock reading.\n\nDURATION SUBTRACTION ALSO NORMALIZES\n  gap <- 2024-06-15T14:00:00-04:00 - 2024-06-15T12:00:00Z\nThe gap is the actual elapsed time between the two instants, accounting for timezones.\n\nWHY THIS MATTERS\nIn JavaScript, comparing Date objects from different timezone strings is unreliable. In some databases, comparing TIMESTAMP WITHOUT TIME ZONE columns across regions gives wrong results. EK9 eliminates this entire category of bugs by always comparing the underlying UTC instant.\n\nSee Q541 for timezone basics. See Q542 for general date comparison. See Q545 for timezone conversion. See Q553 for common timezone mistakes.","ek9Example":"defines module qa.compare.across.timezones\n\n  defines program\n    CompareAcrossTimezonesDemo()\n      stdout <- Stdout()\n\n      // Same instant, different timezones\n      utcNoon <- 2024-06-15T12:00:00Z\n      nyMorning <- 2024-06-15T08:00:00-04:00\n      tokyoEvening <- 2024-06-15T21:00:00+09:00\n\n      // All represent 12:00 UTC\n      require utcNoon == nyMorning\n      require utcNoon == tokyoEvening\n      require nyMorning == tokyoEvening\n      stdout.println(`UTC noon == NY 8am: ${utcNoon == nyMorning}`)\n      stdout.println(`UTC noon == Tokyo 9pm: ${utcNoon == tokyoEvening}`)\n\n      // Different instants, different timezones\n      laterUTC <- 2024-06-15T14:00:00Z\n      require laterUTC > nyMorning\n      require laterUTC > tokyoEvening\n      stdout.println(`14:00 UTC > 08:00 NY: ${laterUTC > nyMorning}`)\n\n      // Spaceship operator also normalizes\n      ordering <- nyMorning <=> tokyoEvening\n      require ordering == 0\n      stdout.println(`NY <=> Tokyo: ${ordering}`)\n\n      // Duration subtraction normalizes\n      event1 <- 2024-06-15T10:00:00-04:00\n      event2 <- 2024-06-15T16:00:00+02:00\n      // event1 = 14:00 UTC, event2 = 14:00 UTC\n      gap <- event2 - event1\n      stdout.println(`Gap: ${gap}`)\n      require event1 == event2\n\n      // Collection of DateTimes from different timezones\n      times as List of DateTime := [laterUTC, nyMorning, utcNoon]\n      unsetDt <- DateTime()\n      firstTime <- times.getOrDefault(0, unsetDt)\n      lastIdx <- length times - 1\n      lastTime <- times.getOrDefault(lastIdx, unsetDt)\n      stdout.println(`First: ${firstTime}`)\n      stdout.println(`Last: ${lastTime}`)","migrationContext":"Java: ZonedDateTime.isEqual() compares instants correctly, but == compares object identity (wrong). Python: timezone-aware datetimes compare correctly, but mixing naive and aware raises TypeError. JavaScript: Date comparison uses UTC internally but timezone string parsing is inconsistent. Go: time.Equal() normalizes, but == does not (compares location too). EK9: all comparison operators automatically normalize to UTC.","keywords":["across","compare","different","duration","equal","instant","migrate","normalize","time","timezone","utc"],"primaryTopics":[],"typicalErrors":[{"error":"E50060","correct":"require utcNoon == nyMorning","incorrect":"require utcNoon.isEqual(nyMorning)","explanation":"DateTime has no isEqual() method. EK9 uses the == operator directly, which automatically normalizes both sides to UTC before comparing. See ek9 -h E50060 for details."}],"companions":[]}
{"id":549,"category":"Date, Time, and Duration","question":"How do I format dates for different locales in EK9?","url":"https://ek9.io/qa/QA0549.html","alternatePhrasings":["How do I display dates in German or French format?","How do locale formatting levels work with dates?","How do I internationalize date display?"],"answer":"EK9's Locale type provides four named formatting levels that automatically adapt to regional conventions. No format pattern strings needed.\n\nFOUR FORMATTING LEVELS FOR DATES\n  shortFormat: compact numeric (03/10/2020 GB, 10/3/20 US)\n  mediumFormat: abbreviated month (Oct 3, 2020 US, 3 Oct 2020 GB)\n  longFormat: full month name (3. Oktober 2020 DE)\n  fullFormat: includes day-of-week (Samstag, 3. Oktober 2020 DE)\n\nEach locale adjusts automatically: date order (day/month vs month/day), separators, month names, and day names.\n\nSAME METHODS FOR TIME AND DATETIME\nThe four formatting levels also work on Time and DateTime:\n  locale.shortFormat(09:30:00) gives locale-appropriate time\n  locale.longFormat(2024-06-15T10:30:00Z) gives full date+time+timezone\n\nDAY OF WEEK\n  locale.dayOfWeek(date) returns the localized day name:\n  Locale('de_DE').dayOfWeek(2024-06-15) gives 'Samstag'\n  Locale('en_GB').dayOfWeek(2024-06-15) gives 'Saturday'\n\nWHY NO PATTERN STRINGS\nPattern strings like 'yyyy-MM-dd' or 'dd/MM/yyyy' are error-prone: 'mm' means minutes in Java but months in Excel. Named levels are unambiguous and locale-correct by default.\n\nSee Q44 for the complete Locale formatting guide. See Q537 for ISO 8601 formatting. See Q551 for RFC 7231 HTTP date headers.","ek9Example":"defines module qa.locale.date.formatting\n\n  defines program\n    LocaleDateFormattingDemo()\n      stdout <- Stdout()\n\n      wedding <- 2020-10-03\n      lunchTime <- 12:00:01\n      meeting <- 2024-06-15T10:30:00Z\n\n      // Create locales\n      enGB <- Locale(\"en_GB\")\n      enUS <- Locale(\"en_US\")\n      deutsch <- Locale(\"de_DE\")\n      francais <- Locale(\"fr_FR\")\n\n      // Date formatting - four levels\n      stdout.println(\"=== Date Formatting ===\")\n      stdout.println(`GB short: ${enGB.shortFormat(wedding)}`)\n      stdout.println(`US short: ${enUS.shortFormat(wedding)}`)\n      stdout.println(`GB medium: ${enGB.mediumFormat(wedding)}`)\n      stdout.println(`US medium: ${enUS.mediumFormat(wedding)}`)\n      stdout.println(`DE long: ${deutsch.longFormat(wedding)}`)\n      stdout.println(`FR long: ${francais.longFormat(wedding)}`)\n      stdout.println(`GB full: ${enGB.fullFormat(wedding)}`)\n      stdout.println(`DE full: ${deutsch.fullFormat(wedding)}`)\n\n      // Time formatting\n      stdout.println(\"=== Time Formatting ===\")\n      stdout.println(`GB short time: ${enGB.shortFormat(lunchTime)}`)\n      stdout.println(`US short time: ${enUS.shortFormat(lunchTime)}`)\n      stdout.println(`DE medium time: ${deutsch.mediumFormat(lunchTime)}`)\n\n      // DateTime formatting\n      stdout.println(\"=== DateTime Formatting ===\")\n      stdout.println(`US short DT: ${enUS.shortFormat(meeting)}`)\n      stdout.println(`GB medium DT: ${enGB.mediumFormat(meeting)}`)\n      stdout.println(`DE long DT: ${deutsch.longFormat(meeting)}`)\n      stdout.println(`FR full DT: ${francais.fullFormat(meeting)}`)\n\n      // Day of week\n      stdout.println(\"=== Day of Week ===\")\n      stdout.println(`GB: ${enGB.dayOfWeek(wedding)}`)\n      stdout.println(`DE: ${deutsch.dayOfWeek(wedding)}`)\n      stdout.println(`FR: ${francais.dayOfWeek(wedding)}`)","migrationContext":"Java: DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT).withLocale(). Python: babel.dates.format_date(style='short', locale='de_DE'). JavaScript: Intl.DateTimeFormat with options. Go: no built-in locale formatting. Rust: no built-in, chrono-locale crate. EK9: Locale('de_DE').shortFormat(date) four named levels, no pattern strings.","keywords":["date","dayOfWeek","duration","format","fullFormat","i18n","internationalization","locale","longFormat","mediumFormat","migrate","regional","shortFormat","time","timezone"],"primaryTopics":[],"typicalErrors":[{"error":"E50060","correct":"stdout.println(`GB short: ${enGB.shortFormat(wedding)}`)","incorrect":"stdout.println(`GB short: ${enGB.format(wedding, \"short\")}`)","explanation":"Locale has no generic format() method. Use the four named methods: shortFormat(), mediumFormat(), longFormat(), fullFormat(). See ek9 -h E50060 for details."}],"companions":[]}
{"id":550,"category":"Date, Time, and Duration","question":"How do I handle calendar edge cases like month-end and leap years in EK9?","url":"https://ek9.io/qa/QA0550.html","alternatePhrasings":["Does EK9 handle leap years correctly?","What happens when adding a month to January 31st?","Why is month arithmetic non-associative in other languages?"],"answer":"EK9 uses a 30-day month approximation for Duration arithmetic. This makes month arithmetic predictable and associative, at the cost of exact calendar precision.\n\nTHE DURATION SUMMING PROBLEM (Deep Dive)\nIn Java and Python, month arithmetic uses true calendar months:\n  Java: Jan 31 + 1 month = Feb 28 (clamped)\n        Feb 28 + 1 month = Mar 28\n  But:  Jan 31 + 2 months = Mar 31 (different!)\n\nThis happens because each addition clamps to the month's length, and the clamping at each intermediate step loses information. Month arithmetic is NOT associative: (a + b) + c is not equal to a + (b + c). This creates subtle bugs when accumulating monthly intervals.\n\nEK9'S APPROACH\nP1M = exactly 30 days (30 x 86400 seconds). Always.\nP1Y = 360 days (12 x 30). Always.\n\nThis means:\n  date + P1M + P1M = date + 60 days\n  date + P2M = date + 60 days (same!)\nMonth arithmetic IS associative. P1M * 2 == P2M. Duration addition is commutative and associative. No surprises from intermediate clamping.\n\nTHE TRADE-OFF\nEK9 durations are predictable but approximate:\n  Feb 1 + P1M = Mar 3 (30 days from Feb 1), not Feb 28\n  Jan 31 + P1M = Mar 2 (30 days from Jan 31), not Feb 28\n\nFor calendar-precise month logic, construct new dates from components:\n  currentMonth <- date.month()\n  nextMonthDate <- Date(date.year(), currentMonth + 1, date.day())\nThis uses the calendar's own month boundaries.\n\nLEAP YEARS\nDate handles leap years correctly in its calendar:\n  Date(2024, 02, 29) is valid (2024 is a leap year)\n  Date(2023, 02, 29) is invalid (produces unset)\nBut Duration arithmetic with P1Y adds 360 days, not 365 or 366.\n\nDESIGN RATIONALE\nMost duration use cases (scheduling, timeouts, intervals) need predictability more than calendar precision. 'Add 2 months' meaning '60 days' is always correct for billing cycles, subscription periods, and timeout calculations. True calendar month arithmetic is only needed for UI features like 'same day next month'.\n\nSee Q32 for Duration basics. See Q540 for the duration summing pitfall introduction. See Q539 for date subtraction. See Q556 for Time wrapping.","ek9Example":"defines module qa.calendar.edge.cases\n\n  defines program\n    CalendarEdgeCasesDemo()\n      stdout <- Stdout()\n\n      // Leap year handling\n      leapDate <- Date(2024, 02, 29)\n      stdout.println(`Leap day 2024: ${leapDate}`)\n      require leapDate?\n\n      // Non-leap year: Feb 29 is invalid -> unset\n      noLeap <- Date(2023, 02, 29)\n      require ~noLeap?\n      stdout.println(`Feb 29 2023 isSet: ${noLeap?}`)\n\n      // P1M = 30 days, always\n      jan31 <- 2024-01-31\n      plusOneMonth <- jan31 + P1M\n      plusTwoMonths <- jan31 + P2M\n      plusOneOne <- jan31 + P1M + P1M\n      stdout.println(`Jan 31 + P1M: ${plusOneMonth}`)\n      stdout.println(`Jan 31 + P2M: ${plusTwoMonths}`)\n      stdout.println(`Jan 31 + P1M + P1M: ${plusOneOne}`)\n\n      // Associative: P1M + P1M == P2M\n      require plusTwoMonths == plusOneOne\n      stdout.println(`P2M == P1M + P1M: ${plusTwoMonths == plusOneOne}`)\n\n      // Duration math is predictable\n      oneMonth <- P1M\n      twoMonths <- P2M\n      require oneMonth + oneMonth == twoMonths\n      require oneMonth * 2 == twoMonths\n      stdout.println(`P1M + P1M == P2M: ${oneMonth + oneMonth == twoMonths}`)\n\n      // P1Y = 360 days\n      oneYear <- P1Y\n      twelveMonths <- P1M * 12\n      require oneYear == twelveMonths\n      stdout.println(`P1Y == P1M * 12: ${oneYear == twelveMonths}`)\n\n      // Calendar-precise month logic: construct from components\n      baseDate <- 2024-01-31\n      nextMonth <- Date(baseDate.year(), baseDate.month() + 1, 28)\n      stdout.println(`Calendar next month end: ${nextMonth}`)\n\n      // Month-end dates\n      march31 <- 2024-03-31\n      plus30days <- march31 + P1M\n      stdout.println(`Mar 31 + P1M (30 days): ${plus30days}`)","migrationContext":"Java: YearMonth, LocalDate.plusMonths() handles month-end clamping (Jan 31 + 1M = Feb 28) but is non-associative. Period.ofMonths(1) stored as months, applied at calculation time. Python: relativedelta(months=+1) same clamping behavior. Go: AddDate(0,1,0) normalizes (Jan 31 + 1M = Mar 2 or 3 depending on Feb). EK9: P1M = 30 days always, associative, predictable. Calendar-precise month logic via component construction.","keywords":["30 days","associative","calendar","clamping","duration","edge case","leap year","migrate","month-end","predictable","summing","time","timezone"],"primaryTopics":[],"typicalErrors":[{"error":"E50060","correct":"require oneMonth + oneMonth == twoMonths","incorrect":"require oneMonth.plus(oneMonth) == twoMonths","explanation":"Duration has no plus() method. EK9 uses the + operator for duration arithmetic. See ek9 -h E50060 for details."}],"companions":[]}
{"id":551,"category":"Date, Time, and Duration","question":"How do I generate RFC 7231 HTTP date headers in EK9?","url":"https://ek9.io/qa/QA0551.html","alternatePhrasings":["How do I format dates for HTTP headers?","How does rfc7231() work in EK9?","How do I create an HTTP Date header value?"],"answer":"EK9 provides a built-in rfc7231() method on DateTime that produces the exact format required by HTTP headers.\n\nONE-LINER\n  httpDate <- DateTime().now().rfc7231()\nProduces: 'Sat, 15 Jun 2024 14:30:00 GMT'\n\nTHE RFC 7231 FORMAT\nHTTP/1.1 requires dates in the format: 'Day, DD Mon YYYY HH:MM:SS GMT'\nExamples:\n  'Sat, 15 Jun 2024 14:30:00 GMT'\n  'Wed, 25 Dec 2024 00:00:00 GMT'\nThe value is always in GMT (UTC). The rfc7231() method automatically converts to UTC regardless of the DateTime's timezone.\n\nWHERE HTTP DATES ARE USED\n  Date: response header (when the response was generated)\n  Last-Modified: when the resource was last changed\n  Expires: when the cached response becomes stale\n  If-Modified-Since: conditional request header\n  Retry-After: when to retry a failed request\n\nWHY BUILT-IN\nEvery HTTP-serving application needs this format. In Java, you need DateTimeFormatter.RFC_1123_DATE_TIME. In Go, you use time.RFC1123. Making it a one-liner on DateTime eliminates a common boilerplate pattern.\n\nSee Q537 for general date formatting. See Q549 for locale formatting. See Q541 for timezone handling.","ek9Example":"defines module qa.rfc7231.http.headers\n\n  defines program\n    Rfc7231HttpHeadersDemo()\n      stdout <- Stdout()\n\n      // Generate RFC 7231 HTTP date from current time\n      now <- DateTime().now()\n      httpDate <- now.rfc7231()\n      stdout.println(`Date: ${httpDate}`)\n\n      // From a specific DateTime\n      event <- 2024-06-15T14:30:00Z\n      stdout.println(`Event: ${event.rfc7231()}`)\n\n      // Works from any timezone - always converts to GMT\n      nyEvent <- 2024-12-25T00:00:00-05:00\n      stdout.println(`NY event: ${nyEvent.rfc7231()}`)\n\n      // Typical HTTP response headers\n      stdout.println(`Date: ${DateTime().now().rfc7231()}`)\n      stdout.println(`Last-Modified: ${event.rfc7231()}`)\n\n      // The result is a String\n      headerValue <- event.rfc7231()\n      require headerValue?\n      stdout.println(`Type check: ${headerValue}`)","migrationContext":"Java: DateTimeFormatter.RFC_1123_DATE_TIME.format(ZonedDateTime.now(ZoneOffset.UTC)). Python: email.utils.formatdate(usegmt=True). JavaScript: new Date().toUTCString(). Go: t.UTC().Format(time.RFC1123). Rust: chrono format with custom pattern. EK9: dateTime.rfc7231() one-liner, always GMT.","keywords":["api","date","duration","expires","format","gmt","header","http","last-modified","migrate","rfc7231","time","timezone","web"],"primaryTopics":[],"typicalErrors":[{"error":"E50060","correct":"httpDate <- now.rfc7231()","incorrect":"httpDate <- now.toRFC1123()","explanation":"DateTime has no toRFC1123() method. EK9 uses rfc7231() which produces the HTTP-standard date format. See ek9 -h E50060 for details."}],"companions":[]}
{"id":552,"category":"Date, Time, and Duration","question":"How does Date promote to DateTime in EK9?","url":"https://ek9.io/qa/QA0552.html","alternatePhrasings":["How do I convert a Date to DateTime?","What does the #^ promote operator do with Date?","How do I extract Date and Time from DateTime?"],"answer":"Date automatically promotes to DateTime via the #^ operator. DateTime can also be decomposed back into its Date and Time parts.\n\nDATE TO DATETIME PROMOTION\nThe #^ promote operator converts Date to DateTime (midnight UTC):\n  birthday <- 1971-02-01\n  birthdayDT <- #^ birthday\n  birthdayDT is 1971-02-01T00:00:00Z\n\nAutomatic promotion also occurs when assigning Date to a DateTime variable:\n  dtValue as DateTime: birthday\n  dtValue is 1971-02-01T00:00:00Z\n\nOr via the DateTime(Date) constructor:\n  explicit <- DateTime(birthday)\n\nDATETIME TO DATE AND TIME EXTRACTION\nThe date() method extracts the Date portion:\n  myDate <- myDateTime.date()\n\nThe time() method extracts the Time portion:\n  myTime <- myDateTime.time()\n\nAlternatively, use extraction operators:\n  #< extracts the Date (lower/coarser part)\n  #> extracts the Time (upper/finer part)\n  datePart <- #< myDateTime\n  timePart <- #> myDateTime\n\nMILLISECOND TO DURATION\nSimilar promotion: Millisecond promotes to Duration via #^:\n  ms <- 5000ms\n  dur <- #^ ms\n  dur is PT5S\n\nSee Q25 for the promote operator in general. See Q31 for Date and Time basics. See Q92 for DateTime with timezones. See Q554 for extracting DateTime components.","ek9Example":"defines module qa.date.promote.datetime\n\n  defines program\n    DatePromoteDateTimeDemo()\n      stdout <- Stdout()\n\n      birthday <- 1971-02-01\n\n      // Explicit promote operator\n      promoted <- #^ birthday\n      stdout.println(`Promoted: ${promoted}`)\n\n      // Assignment promotion\n      dtValue as DateTime: birthday\n      stdout.println(`Assigned: ${dtValue}`)\n\n      // Constructor promotion\n      explicit <- DateTime(birthday)\n      stdout.println(`Constructor: ${explicit}`)\n\n      // All three are equivalent\n      require promoted == dtValue\n      require dtValue == explicit\n\n      // DateTime decomposition\n      meeting <- 2024-06-15T10:30:00Z\n\n      // Method extraction\n      datePart <- meeting.date()\n      timePart <- meeting.time()\n      stdout.println(`Date part: ${datePart}`)\n      stdout.println(`Time part: ${timePart}`)\n\n      // Operator extraction\n      dateExtract <- #< meeting\n      timeExtract <- #> meeting\n      stdout.println(`#< extract: ${dateExtract}`)\n      stdout.println(`#> extract: ${timeExtract}`)\n\n      require datePart == dateExtract\n      require timePart == timeExtract\n\n      // Millisecond to Duration promotion\n      ms <- 5000ms\n      dur <- #^ ms\n      stdout.println(`5000ms promoted: ${dur}`)\n\n      // Round-trip: Date -> DateTime -> Date\n      original <- 2024-03-15\n      asDT as DateTime: original\n      backToDate <- asDT.date()\n      require backToDate == original\n      stdout.println(`Round-trip: ${original} -> ${asDT} -> ${backToDate}`)","migrationContext":"Java: LocalDate.atStartOfDay(ZoneOffset.UTC) to convert Date to DateTime. LocalDateTime.toLocalDate() and toLocalTime() for extraction. Python: datetime.combine(date, time.min) for promotion. Go: no separate Date type. Rust: NaiveDate with_hms() methods. EK9: #^ promotes Date to DateTime automatically, date() and time() extract parts, #< and #> extraction operators.","keywords":["constructor","convert","date","date()","datetime","decompose","duration","extract","promote","time","time()","timezone"],"primaryTopics":[],"typicalErrors":[{"error":"E50060","correct":"datePart <- meeting.date()\n      timePart <- meeting.time()","incorrect":"datePart <- meeting.toDate()\n      timePart <- meeting.toTime()","explanation":"DateTime has no toDate() or toTime() methods. Use date() and time() to extract the Date and Time components. See ek9 -h E50060 for details."}],"companions":[]}
{"id":553,"category":"Date, Time, and Duration","question":"What are common timezone mistakes and how does EK9 prevent them?","url":"https://ek9.io/qa/QA0553.html","alternatePhrasings":["What timezone bugs does EK9 prevent?","How does EK9 handle daylight saving time?","What are the most common timezone errors in programming?"],"answer":"Six common timezone mistakes and how EK9 addresses each one.\n\n1. STORING LOCAL TIME WITHOUT TIMEZONE\nMistake: saving '2024-06-15 10:30' without specifying which timezone.\nJava: LocalDateTime has no timezone, stored values are ambiguous.\nEK9: DateTime always carries a timezone. You cannot create a DateTime without one. Literals require Z or an offset.\n\n2. COMPARING NAIVE AND AWARE DATETIMES\nMistake: comparing a timezone-unaware timestamp with a timezone-aware one.\nPython: raises TypeError when mixing naive and aware datetimes.\nEK9: all DateTimes are timezone-aware. Comparison automatically normalizes to UTC (see Q548).\n\n3. CONFUSING withSameInstant AND withZone\nMistake: using the wrong conversion method, creating a DateTime that represents a different moment than intended.\nJava: withZoneSameInstant() vs withZoneSameLocal() have confusing names.\nEK9: withSameInstant() and withZone() have clear, descriptive names (see Q546).\n\n4. ASSUMING FIXED UTC OFFSETS\nMistake: hardcoding UTC-5 for New York (fails during daylight saving time).\nEK9: use IANA names like 'America/New_York' which automatically handle DST transitions.\n\n5. IGNORING DST IN ARITHMETIC\nMistake: adding 24 hours and expecting to land on the same time tomorrow (fails on DST transition days).\nEK9: Duration arithmetic on DateTime handles DST correctly. Adding P1D adds one calendar day regardless of DST.\n\n6. USING AMBIGUOUS TIMEZONE ABBREVIATIONS\nMistake: using 'EST' (could be US Eastern or Australian Eastern).\nEK9: IANA names are unambiguous. 'America/New_York' and 'Australia/Sydney' are distinct.\n\nSee Q541 for timezone basics. See Q545 for timezone conversion. See Q546 for withSameInstant vs withZone. See Q547 for UTC storage. See Q548 for cross-timezone comparison.","ek9Example":"defines module qa.timezone.mistakes\n\n  defines constant\n    newYorkZone <- \"America/New_York\"\n\n  defines program\n    TimezoneMistakesDemo()\n      stdout <- Stdout()\n\n      // MISTAKE 1: EK9 DateTimes always have a timezone\n      // You cannot create an ambiguous timestamp\n      utcEvent <- 2024-06-15T10:30:00Z\n      offsetEvent <- 2024-06-15T10:30:00-04:00\n      stdout.println(`UTC: ${utcEvent}, zone: ${utcEvent.zone()}`)\n      stdout.println(`Offset: ${offsetEvent}, zone: ${offsetEvent.zone()}`)\n\n      // MISTAKE 2: All comparisons normalize to UTC\n      // No mixing naive and aware - everything is aware\n      require utcEvent <> offsetEvent\n      stdout.println(`Different instants: ${utcEvent <> offsetEvent}`)\n\n      // MISTAKE 3: Clear method names\n      // withSameInstant = same moment, different display\n      nyDisplay <- utcEvent.withSameInstant(newYorkZone)\n      stdout.println(`Same instant NY: ${nyDisplay}`)\n      require nyDisplay == utcEvent\n\n      // withZone = same clock, different moment\n      nyLabel <- utcEvent.withZone(newYorkZone)\n      stdout.println(`Same clock NY: ${nyLabel}`)\n      require nyLabel <> utcEvent\n\n      // MISTAKE 4: Use IANA names, not fixed offsets\n      // newYorkZone handles DST automatically\n      summer <- 2024-06-15T12:00:00Z\n      winter <- 2024-12-15T12:00:00Z\n      nySummer <- summer.withSameInstant(newYorkZone)\n      nyWinter <- winter.withSameInstant(newYorkZone)\n      stdout.println(`NY summer offset: ${nySummer.zone()}`)\n      stdout.println(`NY winter offset: ${nyWinter.zone()}`)\n\n      // MISTAKE 5: Duration arithmetic is calendar-safe\n      beforeDST <- 2024-03-09T12:00:00-05:00\n      plusOneDay <- beforeDST + P1D\n      stdout.println(`Before DST: ${beforeDST}`)\n      stdout.println(`+ P1D: ${plusOneDay}`)\n\n      // MISTAKE 6: IANA names are unambiguous\n      usEast <- utcEvent.withSameInstant(newYorkZone)\n      auEast <- utcEvent.withSameInstant(\"Australia/Sydney\")\n      stdout.println(`US Eastern: ${usEast}`)\n      stdout.println(`AU Eastern: ${auEast}`)","migrationContext":"All languages have timezone pitfalls. Java: LocalDateTime loses timezone, ZonedDateTime methods have confusing names. Python: naive vs aware datetimes, pytz normalize() gotcha. JavaScript: Date has no timezone awareness beyond local/UTC. Go: time.Location is opaque, DST handled internally. EK9: all DateTimes carry timezone, IANA names, clear method names, UTC-normalized comparison.","keywords":["aware","bug","daylight saving","dst","duration","iana","migrate","mistake","naive","offset","prevention","time","timezone"],"primaryTopics":[],"typicalErrors":[{"error":"E50060","correct":"nyDisplay <- utcEvent.withSameInstant(newYorkZone)","incorrect":"nyDisplay <- utcEvent.withZoneSameInstant(newYorkZone)","explanation":"DateTime has no withZoneSameInstant() method (that is the Java naming). EK9 uses the clearer name withSameInstant(). See ek9 -h E50060 for details."}],"companions":[]}
{"id":554,"category":"Date, Time, and Duration","question":"How do I extract date and time components from a DateTime in EK9?","url":"https://ek9.io/qa/QA0554.html","alternatePhrasings":["How do I get the year, month, and day from a DateTime?","How do I access individual parts of a DateTime?","What accessor methods does DateTime have?"],"answer":"DateTime provides accessor methods for all date and time components, plus decomposition methods for Date and Time parts.\n\nDATE COMPONENT ACCESSORS\n  dt.year() returns the year (e.g., 2024)\n  dt.month() returns the month (1-12)\n  dt.day() returns the day of month (1-31)\n  dt.dayOfMonth() same as day()\n  dt.dayOfWeek() returns day of week (1=Monday through 7=Sunday)\n  dt.dayOfYear() returns day of year (1-366)\n\nTIME COMPONENT ACCESSORS\n  dt.hour() returns the hour (0-23)\n  dt.minute() returns the minute (0-59)\n  dt.second() returns the second (0-59)\n\nTIMEZONE ACCESSORS\n  dt.zone() returns the timezone string ('Z', '-05:00', etc.)\n  dt.offSetFromUTC() returns the UTC offset as Duration\n\nDECOMPOSITION METHODS\n  dt.date() extracts the Date portion\n  dt.time() extracts the Time portion\n\nEXTRACTION OPERATORS\n  #< dt extracts Date (the lower/coarser component)\n  #> dt extracts Time (the upper/finer component)\n\nDATE AND TIME ACCESSORS\nDate has: year(), month(), day(), dayOfMonth(), dayOfWeek(), dayOfYear()\nTime has: hour(), minute(), second()\nAll accessors return Integer values that are set when the source is set, and unset when the source is unset.\n\nSee Q31 for Date and Time basics. See Q92 for DateTime. See Q552 for Date-to-DateTime promotion. See Q555 for constructing from components.","ek9Example":"defines module qa.extract.components\n\n  defines program\n    ExtractComponentsDemo()\n      stdout <- Stdout()\n\n      meeting <- 2024-06-15T10:30:45Z\n\n      // Date components\n      stdout.println(`Year: ${meeting.year()}`)\n      stdout.println(`Month: ${meeting.month()}`)\n      stdout.println(`Day: ${meeting.day()}`)\n      stdout.println(`Day of month: ${meeting.dayOfMonth()}`)\n      stdout.println(`Day of week: ${meeting.dayOfWeek()}`)\n      stdout.println(`Day of year: ${meeting.dayOfYear()}`)\n\n      // Time components\n      stdout.println(`Hour: ${meeting.hour()}`)\n      stdout.println(`Minute: ${meeting.minute()}`)\n      stdout.println(`Second: ${meeting.second()}`)\n\n      // Timezone components\n      stdout.println(`Zone: ${meeting.zone()}`)\n      stdout.println(`UTC offset: ${meeting.offSetFromUTC()}`)\n\n      // Decomposition methods\n      datePart <- meeting.date()\n      timePart <- meeting.time()\n      stdout.println(`Date part: ${datePart}`)\n      stdout.println(`Time part: ${timePart}`)\n\n      // Extraction operators\n      dateExtract <- #< meeting\n      timeExtract <- #> meeting\n      require datePart == dateExtract\n      require timePart == timeExtract\n\n      // Date accessors\n      birthday <- 1971-02-01\n      stdout.println(`Birthday year: ${birthday.year()}`)\n      stdout.println(`Birthday month: ${birthday.month()}`)\n      stdout.println(`Birthday day: ${birthday.day()}`)\n\n      // Time accessors\n      alarm <- 07:30:00\n      stdout.println(`Alarm hour: ${alarm.hour()}`)\n      stdout.println(`Alarm minute: ${alarm.minute()}`)\n      stdout.println(`Alarm second: ${alarm.second()}`)\n\n      // Unset propagation\n      unsetDT <- DateTime()\n      stdout.println(`Unset year isSet: ${unsetDT.year()?}`)\n      stdout.println(`Unset hour isSet: ${unsetDT.hour()?}`)","migrationContext":"Java: getYear(), getMonthValue(), getDayOfMonth(), etc. Python: .year, .month, .day attributes. JavaScript: getFullYear(), getMonth() (0-indexed!), getDate(). Go: t.Year(), t.Month(), t.Day(). Rust: chrono year(), month(), day(). EK9: year(), month(), day(), hour(), minute(), second() methods, all 1-indexed, plus date()/time() decomposition.","keywords":["accessor","component","date","datetime","day","decompose","duration","extract","hour","minute","month","second","time","timezone","year","zone"],"primaryTopics":[],"typicalErrors":[{"error":"E50060","correct":"stdout.println(`Year: ${meeting.year()}`)","incorrect":"stdout.println(`Year: ${meeting.getYear()}`)","explanation":"DateTime has no getYear() method (that is the Java naming). EK9 uses year() without the 'get' prefix. See ek9 -h E50060 for details."}],"companions":[]}
{"id":555,"category":"Date, Time, and Duration","question":"How do I construct a DateTime from individual components in EK9?","url":"https://ek9.io/qa/QA0555.html","alternatePhrasings":["What constructor forms does DateTime have?","How do I create a Date from year, month, and day?","How do I build a DateTime programmatically?"],"answer":"Date, Time, and DateTime all support component-based construction.\n\nDATE CONSTRUCTORS\n  Date(year, month, dayOfMonth) from three integers\n  Date(daysFromEpoch) from epoch day count\n  Date(stringValue) from ISO 8601 string\n  Date() unset date\n\nTIME CONSTRUCTORS\n  Time(hour, minute) two components\n  Time(hour, minute, second) three components\n  Time(secondOfDay) from seconds since midnight\n  Time(stringValue) from ISO 8601 string\n  Time() unset time\n\nDATETIME CONSTRUCTORS\n  DateTime(year, month, dayOfMonth) date only (midnight UTC)\n  DateTime(year, month, dayOfMonth, hour) with hour\n  DateTime(year, month, dayOfMonth, hour, minute) with minute\n  DateTime(year, month, dayOfMonth, hour, minute, second) full\n  DateTime(dateValue) from a Date (promotes)\n  DateTime(stringValue) from ISO 8601 string\n  DateTime() unset datetime\n\nLITERAL SYNTAX\nFor known values, literals are more readable:\n  date <- 2024-06-15\n  time <- 10:30:00\n  dt <- 2024-06-15T10:30:00Z\n\nCOMPONENT CONSTRUCTION FOR DYNAMIC VALUES\nWhen values come from variables or calculations:\n  year <- 2024\n  month <- 6\n  dayVal <- 15\n  computed <- Date(year, month, dayVal)\n\nINVALID COMPONENTS PRODUCE UNSET\n  invalid <- Date(2024, 13, 01) produces an unset Date (month 13 does not exist).\n  invalid <- Time(25, 00) produces an unset Time.\n\nSee Q538 for string parsing. See Q552 for Date-to-DateTime promotion. See Q554 for extracting components.","ek9Example":"defines module qa.construct.from.components\n\n  defines program\n    ConstructFromComponentsDemo()\n      stdout <- Stdout()\n\n      // Date constructors\n      fromComponents <- Date(2024, 06, 15)\n      fromEpochDays <- Date(19889)\n      fromString <- Date(\"2024-06-15\")\n      fromLiteral <- 2024-06-15\n      stdout.println(`Components: ${fromComponents}`)\n      stdout.println(`Epoch days: ${fromEpochDays}`)\n      stdout.println(`String: ${fromString}`)\n      stdout.println(`Literal: ${fromLiteral}`)\n\n      // Time constructors\n      twoArgs <- Time(10, 30)\n      threeArgs <- Time(10, 30, 45)\n      fromSecond <- Time(37845)\n      timeLiteral <- 10:30:45\n      stdout.println(`Two args: ${twoArgs}`)\n      stdout.println(`Three args: ${threeArgs}`)\n      stdout.println(`From seconds: ${fromSecond}`)\n      stdout.println(`Literal: ${timeLiteral}`)\n      require threeArgs == timeLiteral\n\n      // DateTime from Date (3 args OK)\n      dateOnly <- DateTime(2024, 06, 15)\n      stdout.println(`Date only: ${dateOnly}`)\n\n      // DateTime from Date promotion then add time via duration\n      dtBase <- DateTime(2024, 06, 15)\n      withMinute <- dtBase + PT10H30M\n      stdout.println(`With minute: ${withMinute}`)\n\n      withSecond <- dtBase + PT10H30M45S\n      stdout.println(`With second: ${withSecond}`)\n\n      // From Date (promotes)\n      datePart <- 2024-06-15\n      fromDate <- DateTime(datePart)\n      stdout.println(`From Date: ${fromDate}`)\n\n      // Dynamic component construction\n      year <- 2024\n      month <- 12\n      dayVal <- 25\n      christmas <- Date(year, month, dayVal)\n      stdout.println(`Dynamic: ${christmas}`)\n\n      // Invalid components produce unset\n      badMonth <- Date(2024, 13, 01)\n      badHour <- Time(25, 00)\n      require ~badMonth?\n      require ~badHour?\n      stdout.println(`Bad month isSet: ${badMonth?}`)\n      stdout.println(`Bad hour isSet: ${badHour?}`)","migrationContext":"Java: LocalDate.of(2024, 6, 15), LocalTime.of(10, 30), ZonedDateTime.of(..., ZoneId.of('UTC')). Python: date(2024, 6, 15), time(10, 30), datetime(2024, 6, 15, 10, 30). JavaScript: new Date(2024, 5, 15) (month is 0-indexed!). Go: time.Date(2024, 6, 15, 10, 30, 0, 0, time.UTC). EK9: Date(2024, 06, 15), Time(10, 30), DateTime(2024, 06, 15, 10, 30) plus literal syntax.","keywords":["build","component","constant","construct","constructor","create","date","day","duration","hour","literal","minute","month","second","time","timezone","year"],"primaryTopics":[],"typicalErrors":[{"error":"E50001","correct":"fromComponents <- Date(2024, 06, 15)","incorrect":"fromComponentsXYZ <- Date(2024, 06, 15)","explanation":"Renaming the variable means later references to 'fromComponents' become unresolved, triggering E50001. See ek9 -h E50001 for details."}],"companions":[]}
{"id":556,"category":"Date, Time, and Duration","question":"How does Time wrapping work in EK9?","url":"https://ek9.io/qa/QA0556.html","alternatePhrasings":["What happens when Time goes past midnight?","Does Time wrap around at 24 hours?","How does Time arithmetic handle overflow?"],"answer":"Time wraps at the 24-hour boundary. Adding time past midnight wraps to the next day's time. Time is a circular clock, not a linear timeline.\n\nBASIC WRAPPING\n  22:00 + PT3H = 01:00 (wraps past midnight)\n  01:00 - PT3H = 22:00 (wraps backwards past midnight)\nTime always stays within 00:00:00 to 23:59:59.\n\nWHY WRAPPING\nTime represents a position on the clock face, not an absolute point in time. A clock does not stop at midnight; it wraps to 00:00. This models the real world correctly: if it is 22:00 and you wait 3 hours, the clock shows 01:00.\n\nWRAPPING WITH INCREMENT/DECREMENT\n  lateNight <- 23:59:59\n  lateNight++ wraps to 00:00:00\n  early <- 00:00:00\n  early-- wraps to 23:59:59\n\nWRAPPING AND SUBTRACTION\nSubtracting a later time from an earlier time produces a negative Duration:\n  gap <- 01:00 - 22:00\n  gap is PT-21H (not PT3H)\nThis is the linear difference, not the 'clock distance'.\n\nDATETIME DOES NOT WRAP\nDateTime (which includes a date) does NOT wrap. Adding PT3H to a DateTime at 22:00 moves to 01:00 on the NEXT DAY. The date changes.\n\nWHEN TO USE TIME VS DATETIME\nTime: schedules, recurring daily events, clock positions.\nDateTime: events with specific dates, timestamps, logging.\n\nSee Q31 for Time basics. See Q540 for date arithmetic. See Q539 for time subtraction. See Q550 for calendar edge cases.","ek9Example":"defines module qa.time.wrapping\n\n  defines program\n    TimeWrappingDemo()\n      stdout <- Stdout()\n\n      // Basic wrapping past midnight\n      evening <- 22:00\n      pastMidnight <- evening + PT3H\n      stdout.println(`22:00 + PT3H = ${pastMidnight}`)\n\n      // Wrapping backwards past midnight\n      earlyMorning <- 01:00\n      lastNight <- earlyMorning - PT3H\n      stdout.println(`01:00 - PT3H = ${lastNight}`)\n\n      // Wrapping with larger durations\n      noon <- 12:00\n      plusDay <- noon + PT24H\n      stdout.println(`12:00 + PT24H = ${plusDay}`)\n\n      plusDayHalf <- noon + PT36H\n      stdout.println(`12:00 + PT36H = ${plusDayHalf}`)\n\n      // Increment wrapping via duration\n      almostMidnight <- 23:59:59\n      almostMidnight += PT1S\n      stdout.println(`23:59:59 + 1s = ${almostMidnight}`)\n\n      // Decrement wrapping via duration\n      justPastMidnight <- 00:00:00\n      justPastMidnight -= PT1S\n      stdout.println(`00:00:00 - 1s = ${justPastMidnight}`)\n\n      // Subtraction gives linear difference (not clock distance)\n      negGap <- 01:00 - 22:00\n      stdout.println(`01:00 - 22:00 = ${negGap}`)\n\n      posGap <- 22:00 - 01:00\n      stdout.println(`22:00 - 01:00 = ${posGap}`)\n\n      // DateTime does NOT wrap - date advances instead\n      eveningDT <- 2024-06-15T22:00:00Z\n      nextDayDT <- eveningDT + PT3H\n      stdout.println(`DateTime 22:00 + 3h = ${nextDayDT}`)\n      // The date changes from June 15 to June 16\n      stdout.println(`Day changed: ${nextDayDT.day()}`)\n\n      // startOfDay and endOfDay\n      dayStart <- Time().startOfDay()\n      dayEnd <- Time().endOfDay()\n      stdout.println(`Start of day: ${dayStart}`)\n      stdout.println(`End of day: ${dayEnd}`)","migrationContext":"Java: LocalTime wraps at midnight (plusHours past midnight wraps). Python: time does not support arithmetic (must use datetime). JavaScript: no Time type, Date wraps dates. Go: no separate Time type, time.Time includes date. Rust: chrono NaiveTime wraps at midnight. EK9: Time wraps at 24h boundary, DateTime does not wrap (date advances).","keywords":["24 hour","arithmetic","circular","clock","duration","midnight","overflow","time","timezone","wrap","wrapping"],"primaryTopics":[],"typicalErrors":[{"error":"E50001","correct":"dayStart <- Time().startOfDay()\n      dayEnd <- Time().endOfDay()","incorrect":"dayStart <- Time.MIN\n      dayEnd <- Time.MAX","explanation":"EK9 has no static field access like Java's Time.MIN or Time.MAX. Type names without () are not resolved as expressions. Use Time().startOfDay() and Time().endOfDay() factory methods. See ek9 -h E50001 for details."}],"companions":[]}
{"id":557,"category":"Code Quality","question":"How does EK9 detect dead code and redundant conditions?","url":"https://ek9.io/qa/QA0557.html","alternatePhrasings":["What is E08086 condition always true?","What is E08087 condition always false?","How does EK9 find unreachable code in if statements?","Why does the compiler reject my redundant condition?"],"answer":"EK9 tracks variable values through control flow and detects conditions whose outcome is already determined. This catches dead code that other languages silently accept.\n\nFLOW-SENSITIVE VALUE TRACKING\nThe compiler tracks what is known about each variable at every program point. When a variable is assigned a constant or narrowed by a condition, the compiler remembers that information on each branch.\n\nCONDITION ALWAYS TRUE (E08086)\nIf the compiler can prove a condition will always be satisfied, it flags it. The else branch would be dead code. Examples: checking x > 5 when x is already known to be greater than 10, or checking x <> 5 in the else branch of 'if x == 5' (the else already implies x is not 5).\n\nCONDITION ALWAYS FALSE (E08087)\nIf the compiler can prove a condition can never be satisfied, it flags it. The if body would be dead code. Examples: checking x == 5 in the else branch of 'if x == 5' (the else already implies x is not 5), or checking x < 0 after throwing on negative values.\n\nWHAT THE COMPILER TRACKS\nInteger range constraints from comparisons (x > 10 narrows to Range(11, MAX)). Equality exclusions from else branches (else of x == 5 means x is anything except 5). Constant values from assignments and named constants. Boolean values from if/else narrowing.\n\nPOST-THROW NARROWING\nWhen an if branch throws, the code after it only executes when the condition was false. The compiler narrows accordingly: after 'if x < 0 throw', the compiler knows x >= 0.\n\nCOMPOUND CONDITIONS\nThe compiler also detects redundant clauses in AND/OR expressions: 'x > 10 and x > 5' has a redundant clause, and 'x > 5 or x <= 5' covers the entire domain.\n\nSee Q558 for specific tautological condition patterns and how to fix them. See Q311 for the full quality checks catalog. See Q318 for self-comparison detection.","ek9Example":"defines module qa.codequality.deadcode\n\n  defines constant\n\n    ADULT_AGE <- 18\n\n    RETIREMENT_AGE <- 65\n\n    FREEZING_POINT <- 0\n\n  defines function\n\n    computeAge()\n      <- rtn as Integer: 30\n\n    <?-\n      Correct: each branch tests a genuinely different condition.\n      The compiler would reject 'if age > 18; if age > 10' because\n      the inner check is always true when the outer is true.\n    -?>\n    classifyLifeStage() as pure\n      -> age as Integer\n      <- stage as String: \"child\"\n\n      if age >= RETIREMENT_AGE\n        stage: \"retired\"\n      else if age >= ADULT_AGE\n        stage: \"adult\"\n\n    <?-\n      Correct: after a throw, the remaining code has narrowed constraints.\n      The compiler accepts the second check because temperature was\n      reassigned from a function call (unknown value).\n    -?>\n    validateAndClassify()\n      -> rawTemperature as Integer\n      <- classification as String: \"above-freezing\"\n\n      if rawTemperature < FREEZING_POINT\n        throw Exception(\"temperature below freezing\")\n\n      adjustedTemp <- computeAge()\n      if adjustedTemp < FREEZING_POINT\n        classification: \"adjusted-below-freezing\"\n\n  defines program\n\n    DeadCodeDetectionDemo()\n      stdout <- Stdout()\n\n      personAge <- 42\n      lifeStage <- classifyLifeStage(personAge)\n      stdout.println(`Age ${personAge}: ${lifeStage}`)\n\n      sensorReading <- 15\n      tempClass <- validateAndClassify(sensorReading)\n      stdout.println(`Temperature ${sensorReading}: ${tempClass}`)","migrationContext":"Java: no flow-sensitive tautology detection in the compiler. SpotBugs has limited constant condition checks. SonarQube detects some always-true conditions but is optional. Rust: compiler warns on some unreachable patterns but not flow-sensitive value tracking. Go: no tautology detection. Python: no dead code detection. C++: some compilers warn on constant conditions with -Wall but not data-flow-based. EK9: mandatory flow-sensitive analysis that tracks values through assignments, branches, and throws.","keywords":["E08086","E08087","always","clean-code","code","condition","dead","false","flow","metric","quality","redundant","tautology","true","unreachable"],"primaryTopics":[],"typicalErrors":[{"error":"E08082","correct":"if age >= RETIREMENT_AGE","incorrect":"if 65 >= 18","explanation":"Comparing two literal values produces a compile-time constant, making the condition pointless dead code. Use variables or named constants. See ek9 -h E08082 for details."},{"error":"E08090","correct":"      lifeStage <- classifyLifeStage(personAge)\n      stdout.println(`Age ${personAge}: ${lifeStage}`)","incorrect":"unused <- classifyLifeStage(personAge)","explanation":"Declaring a variable that is never used is dead code. Every variable must be referenced after declaration. See ek9 -h E08090 for details."}],"companions":[]}
{"id":558,"category":"Code Quality","question":"What are tautological conditions and how do I fix them?","url":"https://ek9.io/qa/QA0558.html","alternatePhrasings":["Why does the compiler say my condition is always true?","What is E08082 constant comparison?","How do I fix a redundant condition in an if statement?","Why is my boolean comparison flagged as tautological?"],"answer":"A tautological condition is one whose outcome is already determined at compile time. EK9 detects these as dead code indicators.\n\nCONSTANT COMPARISON (E08082)\nComparing two literals produces a known result: '3 > 2' is always true. Use a named constant or computed value instead.\n\nCONSTANT ARITHMETIC (E08083)\nArithmetic on two literals produces a constant: '3 + 2' should be expressed as the constant 5. Use a named constant.\n\nREDUNDANT BOOLEAN COMPARISON (E08084)\nComparing a Boolean expression with a literal is redundant: 'if flag == true' should be 'if flag'. The comparison adds nothing.\n\nLOGICAL TAUTOLOGY (E08085)\nLogical expressions that always produce the same result: 'x or not x' is always true. 'x and not x' is always false.\n\nFLOW-SENSITIVE CONDITIONS (E08086/E08087)\nWhen the compiler tracks variable values through control flow, it can prove some conditions are predetermined. After 'if x > 10', checking 'x > 5' inside that branch is always true (E08086). In the else branch of 'if x == 5', checking 'x == 5' is always false (E08087).\n\nCONSTANT COALESCING (E08094)\nCoalescing operators between two literals produce a known result: '5 <? 3' is always 3. The coalescing operators (<?, >?, <=?, >=?) return a value, not a Boolean, but when both operands are literals the result is still predetermined. Use a variable or named constant instead.\n\nCOMMON PATTERNS THAT TRIGGER\nRedundant else re-check: 'if x == 5 ... else if x == 5' — the else already implies x is not 5. Post-throw re-check: after 'if x < 0 throw', checking 'x < 0' again is always false. Implied range: 'if x > 100 ... if x > 10' — the inner check is subsumed by the outer.\n\nHOW TO FIX\nUse named constants instead of raw literals. Remove redundant inner conditions. Use else-if chains where each branch tests a genuinely different range. Trust post-throw narrowing instead of re-checking.\n\nSee Q557 for flow-sensitive dead code detection. See Q311 for the full quality checks catalog. See Q318 for self-comparison detection. See Q637 for safe variable comparison patterns. See Q638 for named constant patterns. See Q639 for Boolean usage without literals. See Q640 for collection isSet check patterns.","ek9Example":"defines module qa.codequality.tautology\n\n  defines constant\n\n    MINIMUM_AGE <- 18\n\n    MAXIMUM_AGE <- 120\n\n    SPEED_LIMIT <- 65\n\n    PENALTY_THRESHOLD <- 80\n\n    DANGER_THRESHOLD <- 100\n\n  defines function\n\n    computeAge()\n      <- rtn as Integer: 25\n\n    <?-\n      Correct: each branch tests a genuinely different range.\n      The compiler would reject 'if age >= 18; if age >= 10' because\n      the inner check would be always true when the outer is true.\n    -?>\n    classifyAge() as pure\n      -> age as Integer\n      <- category as String: \"minor\"\n\n      if age >= MAXIMUM_AGE\n        category: \"centenarian\"\n      else if age >= MINIMUM_AGE\n        category: \"adult\"\n\n    <?-\n      Correct: using named constants for comparisons avoids E08082.\n      Raw literals like 'if speed > 65' would trigger magic literal detection.\n    -?>\n    classifySpeed() as pure\n      -> speed as Integer\n      <- category as String: \"safe\"\n\n      if speed >= DANGER_THRESHOLD\n        category: \"dangerous\"\n      else if speed >= PENALTY_THRESHOLD\n        category: \"ticket-worthy\"\n      else if speed > SPEED_LIMIT\n        category: \"over-limit\"\n\n    <?-\n      Correct: uses a Boolean directly instead of comparing with true/false.\n      Writing 'if isEnabled == true' would trigger E08084.\n    -?>\n    formatFlag() as pure\n      -> isEnabled as Boolean\n      <- label as String: \"disabled\"\n\n      if isEnabled\n        label: \"enabled\"\n\n    <?-\n      Correct: after throwing on invalid input, the remaining code\n      can trust the variable satisfies the inverse condition without re-checking.\n    -?>\n    validateAndProcess()\n      -> score as Integer\n      <- result as String: \"processed\"\n\n      if score < 0\n        throw Exception(\"score must not be negative\")\n\n      adjustedScore <- computeAge()\n      if adjustedScore < 0\n        result: \"adjusted-score-negative\"\n\n  defines program\n\n    TautologicalConditionsDemo()\n      stdout <- Stdout()\n\n      personAge <- 42\n      ageCategory <- classifyAge(personAge)\n      stdout.println(`Age ${personAge}: ${ageCategory}`)\n\n      carSpeed <- 72\n      speedCategory <- classifySpeed(carSpeed)\n      stdout.println(`Speed ${carSpeed}: ${speedCategory}`)\n\n      featureOn <- true\n      flagLabel <- formatFlag(featureOn)\n      stdout.println(`Feature: ${flagLabel}`)\n\n      testScore <- 85\n      outcome <- validateAndProcess(testScore)\n      stdout.println(`Score ${testScore}: ${outcome}`)","migrationContext":"Java: no tautological condition detection in javac. SpotBugs has limited checks. SonarQube detects some constant conditions. Rust: clippy has logic_bug and redundant_pattern lints. Go: no tautology detection. Python: pylint detects some constant conditions. C++: -Wtautological-compare warns on a few patterns. EK9: comprehensive detection covering constant comparison, constant coalescing, boolean redundancy, logical tautology, and flow-sensitive narrowing, all as mandatory compiler errors.","keywords":["E08082","E08083","E08084","E08085","E08086","E08087","E08094","always","clean-code","coalescing","comparison","condition","constant","false","metric","migrate","quality","redundant","tautology","true"],"primaryTopics":[],"typicalErrors":[{"error":"E08084","correct":"if isEnabled","incorrect":"if isEnabled == true","explanation":"Comparing a Boolean to the literal true is redundant. The Boolean IS the condition. Use it directly. See ek9 -h E08084 for details."},{"error":"E08082","correct":"if age >= MAXIMUM_AGE","incorrect":"if 120 >= 18","explanation":"Comparing two literal values produces a compile-time constant. Use named constants compared against variables. See ek9 -h E08082 for details."}],"companions":[]}
{"id":559,"category":"Code Quality","question":"Why does EK9 reject my isSet check as redundant?","url":"https://ek9.io/qa/QA0559.html","alternatePhrasings":["What is E08088 redundant isSet check?","What is E08089 never-set isSet check?","How does EK9 track set and unset variables?","When is the ? operator genuinely needed?"],"answer":"EK9 tracks whether each variable is set, unset, or unknown at every program point. When the compiler can prove the outcome of an isSet check, it flags it as redundant.\n\nREDUNDANT ISSET CHECK (E08088)\nIf a variable is known to be set, checking it with ? is pointless. Examples: a variable assigned from a literal ('name <- hello; if name?'), a variable inside a guard block ('if name <- getName(); if name?' — the guard already proved it is set), or a variable assigned from a constructor ('list <- List(); if list?').\n\nNEVER-SET ISSET CHECK (E08089)\nIf a variable is declared but never assigned, checking it with ? is always false. Example: 'name as String?; if name?' — the variable was declared unset and no assignment occurs before the check.\n\nWHEN ISSET IS GENUINELY NEEDED\nThe ? operator is needed when the variable might or might not be set depending on runtime conditions. A value from a function call may be unset. A value from a Dict lookup may be absent. A value conditionally assigned in only one branch of an if statement needs checking afterward.\n\nGUARD PATTERN REPLACES ISSET\nInstead of 'result <- lookup(key); if result?', use the guard pattern: 'if result <- lookup(key)'. The guard combines the call and the isSet check into one expression, and the variable is only in scope when it is known to be set.\n\nSee Q557 for flow-sensitive dead code detection. See Q558 for tautological condition patterns. See Q311 for the full quality checks catalog. See Q640 for collection isSet check patterns.","ek9Example":"defines module qa.codequality.issetcheck\n\n  defines function\n\n    fetchName()\n      <- rtn as String: \"Alice\"\n\n    fetchOptionalTitle()\n      <- rtn as String: String()\n\n    <?-\n      Correct: isSet check on a value from a function call is genuinely useful\n      because the function might return an unset value.\n    -?>\n    greetPerson()\n      -> name as String\n      <- greeting as String: `Hello ${name}`\n\n      title <- fetchOptionalTitle()\n      if title?\n        greeting: `Hello ${title} ${name}`\n\n    <?-\n      Correct: the guard pattern combines lookup and isSet check.\n      The variable is only in scope when it is known to be set.\n    -?>\n    greetWithGuard()\n      <- greeting as String: \"Hello stranger\"\n\n      if name <- fetchName()\n        greeting: `Hello ${name}`\n\n    <?-\n      Correct: isSet check on a value from another function call\n      where the result might be unset. The compiler does not know\n      the runtime return value, so the check is genuinely needed.\n    -?>\n    buildFullGreeting()\n      -> name as String\n      <- result as String: `Greeting for ${name}`\n\n      optionalSuffix <- fetchOptionalTitle()\n      if optionalSuffix?\n        result: `Dear ${optionalSuffix} ${name}`\n\n  defines program\n\n    RedundantIsSetDemo()\n      stdout <- Stdout()\n\n      personGreeting <- greetPerson(\"Bob\")\n      stdout.println(personGreeting)\n\n      guardGreeting <- greetWithGuard()\n      stdout.println(guardGreeting)\n\n      fullGreeting <- buildFullGreeting(\"Carol\")\n      stdout.println(fullGreeting)","migrationContext":"Java: no compiler tracking of null vs non-null at each program point. Optional.isPresent() is never flagged as redundant. Kotlin: smart casts track nullability but do not flag redundant null checks. Rust: Option matching is exhaustive but redundant Some checks are not flagged. Go: no nil tracking. Python: no None tracking. C++: no nullptr flow analysis. EK9: mandatory flow-sensitive tracking of set/unset state with compiler errors for redundant or always-failing checks.","keywords":["E08088","E08089","check","clean-code","flow","guard","isSet","isset","metric","null-safe","quality","redundant","safe","set","tracking","unset","variable"],"primaryTopics":[],"typicalErrors":[{"error":"E08090","correct":"personGreeting <- greetPerson(\"Bob\")\n      stdout.println(personGreeting)","incorrect":"unused <- greetPerson(\"Bob\")","explanation":"Declaring a variable and never using it is dead code. Every variable must be referenced. See ek9 -h E08090 for details."}],"companions":[]}
{"id":560,"category":"Purity Contracts","question":"What does 'as pure' mean on a method in EK9?","url":"https://ek9.io/qa/QA0560.html","alternatePhrasings":["How do I mark a method as pure in EK9?","What restrictions apply to pure methods?","How does EK9 enforce method purity?"],"answer":"The 'as pure' modifier on a method guarantees that it will not mutate any state through mutation operators. This is a compiler-enforced contract, not a convention.\n\nWHAT PURE MEANS\nA pure method cannot use mutation operators (+=, -=, *=, /=, :=:, :~:, :^:). It can reassign local variables with : (which creates a new value). It cannot call non-pure methods. The compiler enforces all of these rules.\n\nHOW TO DECLARE PURE\nAdd 'as pure' after the method name:\n  calculate() as pure\n    -> x as Integer\n    <- rtn as Integer: x + 1\n\nWHAT PURE ALLOWS\nReassignment with : (creates new binding, not mutation). Creating new values with non-mutating operators (+, -, *, /). Reading fields and parameters. Returning computed values.\n\nWHAT PURE FORBIDS\nMutation operators (+=, -=, *=, /=, :=:, :~:, :^:) are forbidden (E08120). Calling non-pure functions or methods is forbidden (E08130). These are compile-time errors, not runtime checks.\n\nWHY PURE MATTERS\nPure methods are safe to call from any context. They cannot corrupt shared state. They enable compiler optimizations. They create auditable security boundaries.\n\nSee Q273 for purity as a security boundary. See Q54 for pure functions and Consumer vs Acceptor. See Q561 for purity inheritance rules. See Q566 for pure call chain restrictions. See Q612 for dispatcher purity matching requirements. See Q635 for forbidden mutation operators in pure methods.\n\nSee Q666 for injection in pure context.\nSee Q678 for purity override contract. See Q690 for pure call chain.","ek9Example":"defines module qa.purity.basics\n\n  defines class\n\n    Calculator\n      factor <- 1\n\n      Calculator()\n        -> factor as Integer\n        this.factor: factor\n\n      //A pure method: no mutation, just computation\n      compute() as pure\n        -> inputValue as Integer\n        <- result as Integer: inputValue * factor\n\n      //A pure accessor: reads state without modifying it\n      currentFactor() as pure\n        <- rtn as Integer: factor\n\n      //Non-pure method: uses mutation operator\n      adjustFactor()\n        -> delta as Integer\n        factor += delta\n\n      default operator ?\n\n  defines program\n\n    PureMethodBasicsDemo()\n      stdout <- Stdout()\n\n      calc <- Calculator(3)\n      stdout.println(`Factor: ${calc.currentFactor()}`)\n\n      result <- calc.compute(10)\n      stdout.println(`3 * 10 = ${result}`)\n\n      //Non-pure: mutates factor\n      calc.adjustFactor(2)\n      stdout.println(`After adjust: ${calc.currentFactor()}`)\n\n      result2 <- calc.compute(10)\n      stdout.println(`5 * 10 = ${result2}`)","migrationContext":"Java: no purity concept, any method can mutate anything. Python: no purity enforcement. Haskell: all functions pure by default (IO monad for effects). Rust: immutable borrows prevent mutation but no purity marker. Kotlin: no purity concept. EK9: 'as pure' modifier enforces no mutation operators and no non-pure calls at compile time.","keywords":["contract","enforce","immutable","method","mutation","operator","pure","purity","side-effect"],"primaryTopics":["pure","pure function","pure method","side effect free"],"typicalErrors":[{"error":"E50001","correct":"calc <- Calculator(3)","incorrect":"calcXYZ <- Calculator(3)","explanation":"Renaming the variable means later references to 'calc' become unresolved, triggering E50001. See ek9 -h E50001 for details."},{"error":"E50001","correct":"result <- calc.compute(10)","incorrect":"resultXYZ <- calc.compute(10)","explanation":"Renaming the variable means later references to 'result' become unresolved, triggering E50001. See ek9 -h E50001 for details."}],"companions":[]}
{"id":561,"category":"Purity Contracts","question":"Why must an override of a pure method also be pure?","url":"https://ek9.io/qa/QA0561.html","alternatePhrasings":["What is E05150 SUPER_IS_PURE?","What happens if I override a pure method without 'as pure'?","How does purity work with method overriding?"],"answer":"Note: override applies to both methods and operators (e.g., override operator ? as pure, override operator $ as pure). \n\nWhen a parent method is marked 'as pure', every override of that method must also be marked 'as pure'. The compiler enforces this with error E05150.\n\nWHY THIS RULE EXISTS\nCallers of the parent type expect pure behavior. If a child could drop purity, polymorphic calls would break the contract:\n  parent as Shape: Circle()\n  parent.area()  // Caller expects pure, child must honour that\n\nTHE ERROR: E05150\nIf the parent method is pure and the child override is not pure, the compiler reports E05150: the parent method is pure so the override must also be pure.\n\nCORRECT PATTERN\nAlways add 'as pure' to the override when the parent is pure:\n  override area() as pure\n    <- rtn as Float: ...\n\nBEHAVIORAL SUBSTITUTION\nThis enforces the Liskov Substitution Principle for purity. Anywhere a Parent is used, a Child can replace it without violating purity expectations.\n\nSee Q560 for pure method basics. See Q562 for the opposite rule (cannot add pure). See Q567 for purity through inheritance chains. See Q568 for pure operators and overrides.","ek9Example":"defines module qa.purity.overriderequired\n\n  defines class\n\n    Shape as abstract\n      //Pure method in parent: all overrides must also be pure\n      area() as pure abstract\n        <- rtn as Float?\n\n      describe() as pure abstract\n        <- rtn as String?\n\n      default operator ?\n\n    Circle extends Shape\n      radius <- 0.0\n\n      Circle()\n        -> radius as Float\n        this.radius: radius\n\n      //Correct: override of pure method is also pure\n      override area() as pure\n        <- rtn as Float: radius * radius * 3.14159\n\n      override describe() as pure\n        <- rtn as String: `Circle with radius ${radius}`\n\n      default operator ?\n\n    Square extends Shape\n      side <- 0.0\n\n      Square()\n        -> side as Float\n        this.side: side\n\n      //Correct: pure override matches parent purity\n      override area() as pure\n        <- rtn as Float: side * side\n\n      override describe() as pure\n        <- rtn as String: `Square with side ${side}`\n\n      default operator ?\n\n  defines program\n\n    PureOverrideRequiredDemo()\n      stdout <- Stdout()\n\n      //Polymorphic use: callers trust pure contract\n      shapes <- List() of Shape\n      shapes += Circle(5.0)\n      shapes += Square(4.0)\n\n      for shape in shapes\n        stdout.println(`${shape.describe()}: area = ${shape.area()}`)","migrationContext":"Java: no purity concept, method contracts are documentation only. Kotlin: no purity enforcement. Rust: trait methods have no purity marker. EK9: compiler-enforced purity inheritance via E05150.","keywords":["E05150","abstract","child","contract","function","immutable","inheritance","open","override","parent","pure","purity","side-effect","substitution","virtual"],"primaryTopics":[],"typicalErrors":[{"error":"E05150","correct":"override area() as pure","incorrect":"override area()","explanation":"The parent Shape declares area as pure abstract. Overriding without as pure violates the purity contract. All overrides of pure methods must also be pure. See ek9 -h E05150 for details."},{"error":"E05120","correct":"override describe() as pure","incorrect":"describe() as pure","explanation":"When the parent Shape has an abstract describe method, the child must use the override keyword to replace it. Omitting override triggers shadowing detection. See ek9 -h E05120 for details."}],"companions":[]}
{"id":562,"category":"Purity Contracts","question":"Why can't I add 'as pure' to an override when the parent method isn't pure?","url":"https://ek9.io/qa/QA0562.html","alternatePhrasings":["What is E05160 SUPER_IS_NOT_PURE?","Can I make a child method pure if the parent is not?","Why does EK9 prevent adding purity in an override?"],"answer":"You cannot add 'as pure' to an override when the parent method is not pure. The compiler reports E05160.\n\nWHY YOU CANNOT ADD PURE\nCallers using the parent type expect the method MAY have side effects. If the child adds purity, code using a parent reference cannot rely on it because another child might not be pure. Purity is a contract from the top of the hierarchy, not something added partway down.\n\nTHE ERROR: E05160\nIf you mark an override 'as pure' but the parent method is not pure, the compiler reports E05160: the parent method is not pure so you cannot add purity.\n\nCORRECT APPROACH\nOption 1: Remove 'as pure' from the override. The child matches the parent contract.\nOption 2: If you control the parent, make the parent method pure. Then all children must follow.\n\nDESIGN PRINCIPLE\nPurity flows DOWN the hierarchy, not UP. The root type decides whether the contract is pure. Children honour it; they cannot change it in either direction.\n\nSee Q561 for the reverse rule (must keep pure). See Q560 for pure method basics. See Q567 for purity through inheritance chains.","ek9Example":"defines module qa.purity.cannotaddpure\n\n  defines class\n\n    //Parent is NOT pure: children must match\n    Logger as open\n      logMessage()\n        -> message as String\n        <- formatted as String: \"[LOG] \" + message\n\n      default operator ?\n\n    //Correct: child does NOT add pure\n    TimestampLogger extends Logger\n      override logMessage()\n        -> message as String\n        <- formatted as String: \"[LOG:TS] \" + message\n\n      default operator ?\n\n    //Separate pure hierarchy: designed pure from the start\n    Formatter as abstract\n      format() as pure abstract\n        -> text as String\n        <- rtn as String?\n\n      default operator ?\n\n    UpperFormatter extends Formatter\n      override format() as pure\n        -> text as String\n        <- rtn as String: text.upperCase()\n\n      default operator ?\n\n  defines program\n\n    CannotAddPureDemo()\n      stdout <- Stdout()\n\n      //Non-pure hierarchy\n      logger <- TimestampLogger()\n      stdout.println(logger.logMessage(\"system started\"))\n\n      //Pure hierarchy (designed pure from the start)\n      formatter <- UpperFormatter()\n      stdout.println(formatter.format(\"hello\"))","migrationContext":"Java: no purity concept, no enforcement. Kotlin: no purity. Rust: no purity on trait methods. Go: no purity. EK9: purity is a hierarchical contract enforced by E05160 (cannot add) and E05150 (cannot remove).","keywords":["E05160","abstract","add","contract","forbidden","function","hierarchy","immutable","open","override","parent","pure","purity","side-effect","virtual"],"primaryTopics":[],"typicalErrors":[{"error":"E05160","correct":"override logMessage()","incorrect":"override logMessage() as pure","explanation":"The parent Logger.logMessage is not pure. Adding as pure to the override is forbidden because purity flows down from the top of the hierarchy, not up. See ek9 -h E05160 for details."},{"error":"E05150","correct":"override format() as pure","incorrect":"override format()","explanation":"The parent Formatter.format is declared pure abstract. Overriding without as pure would violate the purity contract. See ek9 -h E05150 for details."}],"companions":[]}
{"id":563,"category":"Purity Contracts","question":"How do pure constructors work and why use ':=?' for field assignment?","url":"https://ek9.io/qa/QA0563.html","alternatePhrasings":["What is E08120 in a pure constructor?","Why does my pure constructor reject ':' for field assignment?","How do I assign fields in a pure constructor?"],"answer":"Pure constructors use ':=?' (guarded assignment) for field assignment, not ':' (direct assignment). The ':=?' operator only assigns if the variable is currently unset, preventing mutation.\n\nWHY ':=?' IN PURE CONSTRUCTORS\nIn a pure constructor, mutation operators are forbidden (E08120). The ':' operator on a field that already has a default value counts as reassignment. The ':=?' operator is safe because it only assigns when the field is unset.\n\nPATTERN FOR PURE CONSTRUCTORS\nDeclare fields with default values, then use ':=?' in the constructor:\n  Config\n    host as String: String()\n    port as Integer: Integer()\n    Config() as pure\n      -> host as String, port as Integer\n      this.host :=? host\n      this.port :=? port\n\nWHY THIS WORKS\nThe fields must start UNSET - 'String()', 'Integer()' - and NOT a value such as 0 or \"\". A field given a value is already SET, so ':=?' can never fire and the constructor argument is silently discarded; the compiler rejects that as E08095. The ':=?' checks if the field is unset and assigns only then. After the first assignment, the field is set and ':=?' becomes a no-op. This is idempotent and non-mutating.\n\nALL OR NOTHING RULE\nIf ANY constructor is pure, ALL constructors must be pure (see Q564). This prevents inconsistent construction semantics.\n\nSee Q560 for pure method basics. See Q564 for all-or-nothing constructor purity. See Q565 for multiple pure constructors. See Q79 for guarded assignment operator.","ek9Example":"defines module qa.purity.constructorassign\n\n  defines class\n\n    Config\n      host as String: String()\n      port as Integer: Integer()\n\n      //Pure constructor using :=? for field assignment\n      Config() as pure\n        ->\n          host as String\n          port as Integer\n        this.host :=? host\n        this.port :=? port\n\n      host() as pure\n        <- rtn as String: host\n\n      port() as pure\n        <- rtn as Integer: port\n\n      default operator ?\n\n    //Another example: immutable value object\n    Point\n      xCoord as Float: Float()\n      yCoord as Float: Float()\n\n      Point() as pure\n        ->\n          xCoord as Float\n          yCoord as Float\n        this.xCoord :=? xCoord\n        this.yCoord :=? yCoord\n\n      xCoord() as pure\n        <- rtn as Float: xCoord\n\n      yCoord() as pure\n        <- rtn as Float: yCoord\n\n      distanceFromOrigin() as pure\n        <- rtn as Float: sqrt(xCoord * xCoord + yCoord * yCoord)\n\n      default operator ?\n\n  defines program\n\n    PureConstructorAssignDemo()\n      stdout <- Stdout()\n\n      config <- Config(\"localhost\", 8080)\n      stdout.println(`Host: ${config.host()}, Port: ${config.port()}`)\n\n      point <- Point(3.0, 4.0)\n      stdout.println(`Point: (${point.xCoord()}, ${point.yCoord()})`)\n      stdout.println(`Distance: ${point.distanceFromOrigin()}`)","migrationContext":"Java: no pure constructors, no guarded assignment. Python: no purity concept. Rust: constructors are always pure (no mutation after move). Kotlin: no pure constructors. EK9: pure constructors with ':=?' for idempotent field initialization, E08120 prevents mutation in pure context.","keywords":["E08120","assign","constant","constructor","contract","field","guarded","idempotent","immutable","isset","mutation","null-safe","pure","purity","safe","side-effect"],"primaryTopics":[],"typicalErrors":[{"error":"E08095","correct":"host as String: String()","incorrect":"host as String: \"localhost\"","explanation":"Giving the field a value at declaration makes it SET, so the ':=?' in the constructor can never apply and the 'host' argument is silently discarded - the object keeps \"localhost\" whatever you construct it with. Fields assigned by a pure constructor must be declared UNSET. See ek9 -h E08095 for details."},{"error":"E08100","correct":"this.host :=? host","incorrect":"this.host: host","explanation":"In a pure constructor, direct assignment with : counts as reassignment of a field. The guarded assignment :=? must be used instead because it only assigns when the field is unset. See ek9 -h E08100 for details."},{"error":"E50001","correct":"config <- Config(\"localhost\", 8080)","incorrect":"configXYZ <- Config(\"localhost\", 8080)","explanation":"Renaming the variable means later references to 'config' become unresolved, triggering E50001. See ek9 -h E50001 for details."}],"companions":[]}
{"id":564,"category":"Purity Contracts","question":"Why must all constructors be pure if any one is pure?","url":"https://ek9.io/qa/QA0564.html","alternatePhrasings":["What is E05190 mix of pure and not pure constructors?","Can I have some pure and some non-pure constructors?","What is the all-or-nothing constructor purity rule?"],"answer":"EK9 enforces an all-or-nothing rule for constructor purity. If any constructor in a class is marked 'as pure', then every constructor must be marked 'as pure'. The compiler reports E05190 for violations.\n\nWHY ALL OR NOTHING\nFactory methods and delegation chains might call any constructor. If purity is inconsistent, the caller cannot know whether construction had side effects. Consistent purity makes object creation predictable.\n\nTHE ERROR: E05190\nIf one constructor is pure and another is not, the compiler reports E05190: mix of pure and not pure constructors.\n\nCORRECT PATTERN: ALL PURE\nMake every constructor pure if you want pure construction:\n  MyClass() as pure\n    -> a as String\n    ...\n  MyClass() as pure\n    -> a as String, b as Integer\n    ...\n\nCORRECT PATTERN: NONE PURE\nMake no constructors pure if construction needs side effects:\n  MyClass()\n    -> a as String\n    ...\n  MyClass()\n    -> a as String, b as Integer\n    ...\n\nSee Q563 for pure constructor field assignment. See Q565 for multiple pure constructors. See Q586 for pure constructor delegation.","ek9Example":"defines module qa.purity.allornothing\n\n  defines class\n\n    //Correct: ALL constructors are pure\n    Coordinate\n      xPos as Float: Float()\n      yPos as Float: Float()\n\n      Coordinate() as pure\n        ->\n          xPos as Float\n          yPos as Float\n        this.xPos :=? xPos\n        this.yPos :=? yPos\n\n      //Second pure constructor: also uses :=?\n      Coordinate() as pure\n        -> xPos as Float\n        this.xPos :=? xPos\n\n      xPos() as pure\n        <- rtn as Float: xPos\n\n      yPos() as pure\n        <- rtn as Float: yPos\n\n      default operator ?\n\n    //Correct: NO constructors are pure\n    LoggedEntity\n      entityName <- String()\n\n      LoggedEntity()\n        -> entityName as String\n        this.entityName: entityName\n\n      LoggedEntity()\n        ->\n          entityName as String\n          prefix as String\n        this.entityName: prefix + entityName\n\n      entityName() as pure\n        <- rtn as String: entityName\n\n      default operator ?\n\n  defines program\n\n    AllOrNothingDemo()\n      stdout <- Stdout()\n\n      //All-pure construction\n      coord1 <- Coordinate(3.0, 4.0)\n      coord2 <- Coordinate(5.0)\n      stdout.println(`Coord1: (${coord1.xPos()}, ${coord1.yPos()})`)\n      stdout.println(`Coord2: (${coord2.xPos()}, ${coord2.yPos()})`)\n\n      //No-pure construction\n      entity1 <- LoggedEntity(\"server\")\n      entity2 <- LoggedEntity(\"server\", \"prod-\")\n      stdout.println(`Entity1: ${entity1.entityName()}`)\n      stdout.println(`Entity2: ${entity2.entityName()}`)","migrationContext":"Java: no constructor purity concept. Python: no purity. Rust: constructors are always effectively pure. Kotlin: no constructor purity. EK9: all-or-nothing constructor purity enforced by E05190.","keywords":["E05190","all-or-nothing","consistent","constant","constructor","contract","factory","immutable","mix","pure","purity","side-effect"],"primaryTopics":[],"typicalErrors":[{"error":"E05190","correct":"Coordinate() as pure\n        -> xPos as Float","incorrect":"Coordinate()\n        -> xPos as Float","explanation":"Since the two-parameter Coordinate constructor is pure, the one-parameter constructor must also be pure. Mixing pure and non-pure constructors triggers E05190. See ek9 -h E05190 for details."},{"error":"E50001","correct":"coord1 <- Coordinate(3.0, 4.0)","incorrect":"coord1XYZ <- Coordinate(3.0, 4.0)","explanation":"Renaming the variable means later references to 'coord1' become unresolved, triggering E50001. See ek9 -h E50001 for details."}],"companions":[]}
{"id":565,"category":"Purity Contracts","question":"How do I write multiple pure constructors with delegation?","url":"https://ek9.io/qa/QA0565.html","alternatePhrasings":["Can pure constructors use this() delegation?","How do I chain pure constructors?","What is the pattern for overloaded pure constructors?"],"answer":"Multiple pure constructors can delegate to each other using this(), just like non-pure constructors. The delegated-to constructor must also be pure (since all constructors must have consistent purity).\n\nDELEGATION IN PURE CONSTRUCTORS\nPure constructors can call this() as the first statement:\n  Address() as pure\n    -> street as String\n    this(street, \"Unknown\")\n  Address() as pure\n    -> street as String, city as String\n    this.street :=? street\n    this.city :=? city\nThe delegating constructor calls another pure constructor.\n\nWHY DELEGATION WORKS\nSince E05190 requires all constructors to be consistently pure, the target of this() is guaranteed to be pure. No purity violation can occur.\n\nPATTERN: DEFAULT VALUES\nUse delegation to provide sensible defaults:\n  Config() as pure\n    this(\"localhost\", 8080)\n  Config() as pure\n    -> host as String\n    this(host, 8080)\n  Config() as pure\n    -> host as String, port as Integer\n    this.host :=? host\n    this.port :=? port\n\nSee Q563 for pure constructor field assignment. See Q564 for all-or-nothing purity. See Q580 for this() delegation in general. See Q586 for pure constructor delegation with super().","ek9Example":"defines module qa.purity.multiplepure\n\n  defines class\n\n    Address\n      street <- String()\n      city <- String()\n      postcode <- String()\n\n      //Minimal constructor delegates to two-arg\n      Address() as pure\n        -> street as String\n        this(street, \"Unknown\")\n\n      //Two-arg delegates to three-arg\n      Address() as pure\n        ->\n          street as String\n          city as String\n        this(street, city, \"N/A\")\n\n      //Full constructor: assigns all fields\n      Address() as pure\n        ->\n          street as String\n          city as String\n          postcode as String\n        this.street :=? street\n        this.city :=? city\n        this.postcode :=? postcode\n\n      street() as pure\n        <- rtn as String: street\n\n      city() as pure\n        <- rtn as String: city\n\n      postcode() as pure\n        <- rtn as String: postcode\n\n      default operator ?\n\n  defines program\n\n    MultiplePureConstructorsDemo()\n      stdout <- Stdout()\n\n      addr1 <- Address(\"123 High St\")\n      stdout.println(`${addr1.street()}, ${addr1.city()}, ${addr1.postcode()}`)\n\n      addr2 <- Address(\"456 Low St\", \"London\")\n      stdout.println(`${addr2.street()}, ${addr2.city()}, ${addr2.postcode()}`)\n\n      addr3 <- Address(\"789 Mid St\", \"Paris\", \"75001\")\n      stdout.println(`${addr3.street()}, ${addr3.city()}, ${addr3.postcode()}`)","migrationContext":"Java: constructor chaining with this() works but no purity concept. Kotlin: constructor delegation but no purity enforcement. EK9: this() delegation in pure constructors, all constructors must be pure (E05190), :=? for field assignment.","keywords":["E05190","chain","constant","constructor","contract","default","delegation","immutable","overload","pure","purity","side-effect","this"],"primaryTopics":[],"typicalErrors":[{"error":"E05190","correct":"Address() as pure\n        -> street as String\n        this(street, \"Unknown\")","incorrect":"Address()\n        -> street as String\n        this(street, \"Unknown\")","explanation":"All constructors must be consistently pure. If the full three-argument constructor is pure, the delegating one-argument constructor must also be declared pure. See ek9 -h E05190 for details."},{"error":"E08100","correct":"this.street :=? street","incorrect":"this.street: street","explanation":"In a pure constructor, direct assignment with : counts as reassignment. The guarded assignment :=? must be used for field initialization to maintain purity. See ek9 -h E08100 for details."}],"companions":[]}
{"id":566,"category":"Purity Contracts","question":"What can and cannot be called from a pure method?","url":"https://ek9.io/qa/QA0566.html","alternatePhrasings":["What is E08130 non-pure call in pure scope?","Can a pure method call a non-pure method?","How does EK9 enforce pure call chains?"],"answer":"A pure method can only call other pure methods and functions. Calling a non-pure method from a pure context produces error E08130.\n\nTHE RULE\nPure can only call pure. If method A is pure, every method A calls must also be pure. This creates a trust chain: if the entry point is pure, the entire execution path is pure.\n\nTHE ERROR: E08130\nCalling a non-pure function or method from within a pure context produces E08130: non-pure call in pure scope.\n\nWHAT PURE CAN CALL\nOther pure methods on the same class. Pure methods on other objects. Built-in pure operators (+, -, *, /, comparisons). Pure functions defined in the module.\n\nWHAT PURE CANNOT CALL\nNon-pure methods (E08130). Methods that use mutation operators internally. Functions not marked as pure.\n\nI/O EXCEPTION\nStdout, Stderr, Stdin, and other I/O types carry the IO marker trait. Methods on IO types are allowed in pure context because I/O is considered a separate concern from data mutation.\n\nBUILT-IN PURE METHODS\nMost accessor methods on built-in types (length, upperCase, contains) are pure. Mutation methods (+=, append, etc.) are not.\n\nSee Q560 for pure method basics. See Q561 for purity in overrides. See Q273 for purity as security boundary. See Q54 for Consumer (pure) vs Acceptor (non-pure).","ek9Example":"defines module qa.purity.restrictions\n\n  defines function\n\n    //Pure function: only uses pure operations\n    doubleValue() as pure\n      -> number as Integer\n      <- result as Integer: number + number\n\n    //Pure function calling another pure function\n    quadrupleValue() as pure\n      -> number as Integer\n      <- result as Integer: doubleValue(doubleValue(number))\n\n  defines class\n\n    Processor\n      prefix <- String()\n\n      Processor()\n        -> prefix as String\n        this.prefix: prefix\n\n      //Pure method: calls only pure functions and methods\n      transform() as pure\n        -> text as String\n        <- rtn as String: `${prefix}: ${text.upperCase()}`\n\n      //Pure method calling pure function\n      processNumber() as pure\n        -> number as Integer\n        <- rtn as String: `${prefix}: ${quadrupleValue(number)}`\n\n      //Non-pure method: this is fine, just cannot be called from pure context\n      logAndTransform()\n        -> text as String\n        <- rtn as String: `${prefix}: ${text.upperCase()}`\n\n      default operator ?\n\n  defines program\n\n    PureMethodRestrictionsDemo()\n      stdout <- Stdout()\n\n      //Pure function calls\n      stdout.println(`Double 5: ${doubleValue(5)}`)\n      stdout.println(`Quadruple 5: ${quadrupleValue(5)}`)\n\n      //Pure method calls\n      proc <- Processor(\"APP\")\n      stdout.println(proc.transform(\"hello\"))\n      stdout.println(proc.processNumber(7))\n\n      //Non-pure method call (allowed from program, which is non-pure)\n      stdout.println(proc.logAndTransform(\"world\"))","migrationContext":"Java: no call chain purity enforcement. Kotlin: no purity tracking. Rust: no purity annotation on function calls. Haskell: pure functions cannot call IO without the monad. EK9: E08130 enforces pure-calls-pure chain, I/O types are exempted via IO trait.","keywords":["E08130","call","chain","contract","function","immutable","migrate","non-pure","pure","purity","restriction","scope","side-effect","trust"],"primaryTopics":[],"typicalErrors":[{"error":"E50001","correct":"proc <- Processor(\"APP\")","incorrect":"procXYZ <- Processor(\"APP\")","explanation":"Renaming the variable means later references to 'proc' become unresolved, triggering E50001. See ek9 -h E50001 for details."},{"error":"E50001","correct":"stdout.println(proc.transform(\"hello\"))","incorrect":"stdout.println(procXYZ.transform(\"hello\"))","explanation":"Referencing a variable 'procXYZ' that was never declared triggers E50001. The original variable was 'proc'. See ek9 -h E50001 for details."}],"companions":[]}
{"id":567,"category":"Purity Contracts","question":"How does purity flow through multi-level inheritance chains?","url":"https://ek9.io/qa/QA0567.html","alternatePhrasings":["Does purity apply to grandchild classes?","How does the pure contract carry through three levels of inheritance?","Must every level maintain purity?"],"answer":"Note: override applies to both methods and operators (e.g., override operator ? as pure, override operator $ as pure). \n\nPurity flows through the entire inheritance chain. If a grandparent method is pure, the parent override must be pure, and the grandchild override must also be pure. Every level must maintain the contract.\n\nMULTI-LEVEL RULE\nGrandparent declares method as pure. Parent override must be pure (E05150 if not). Grandchild override must be pure (E05150 if not). The chain never breaks.\n\nWHY EVERY LEVEL MATTERS\nCode using the grandparent type expects purity. The actual runtime object might be any descendant. Every descendant must honour the original contract.\n\nPRACTICAL PATTERN\nDesign purity at the root of the hierarchy:\n  Animal as abstract\n    sound() as pure abstract\n  Dog extends Animal\n    override sound() as pure\n  Labrador extends Dog\n    override sound() as pure\n\nCANNOT ADD PURE LATER\nIf the grandparent is NOT pure, no descendant can add purity (E05160). Purity is designed in at the top.\n\nSee Q561 for purity override rules. See Q562 for cannot-add-pure. See Q575 for deep override chains. See Q568 for pure operators.\nSee Q677 for abstract implementation chain. See Q678 for purity override contract. See Q699 for return type covariance.","ek9Example":"defines module qa.purity.chaininheritance\n\n  defines class\n\n    //Level 1: declares pure contract\n    Animal as abstract\n      sound() as pure abstract\n        <- rtn as String?\n\n      describe() as pure abstract\n        <- rtn as String?\n\n      default operator ?\n\n    //Level 2: must maintain pure\n    Dog extends Animal as open\n      override sound() as pure\n        <- rtn as String: \"Woof\"\n\n      override describe() as pure\n        <- rtn as String: \"Dog\"\n\n      default operator ?\n\n    //Level 3: must STILL maintain pure\n    Labrador extends Dog\n      override sound() as pure\n        <- rtn as String: \"Woof woof!\"\n\n      override describe() as pure\n        <- rtn as String: \"Labrador\"\n\n      default operator ?\n\n    //Another chain: Cat hierarchy\n    Cat extends Animal as open\n      override sound() as pure\n        <- rtn as String: \"Meow\"\n\n      override describe() as pure\n        <- rtn as String: \"Cat\"\n\n      default operator ?\n\n    Siamese extends Cat\n      override sound() as pure\n        <- rtn as String: \"Mrow!\"\n\n      override describe() as pure\n        <- rtn as String: \"Siamese\"\n\n      default operator ?\n\n  defines program\n\n    PureChainDemo()\n      stdout <- Stdout()\n\n      //All levels honour the pure contract\n      animals <- List() of Animal\n      animals += Labrador()\n      animals += Siamese()\n\n      for animal in animals\n        stdout.println(`${animal.describe()} says: ${animal.sound()}`)","migrationContext":"Java: no multi-level purity tracking. Kotlin: no purity. EK9: purity contract is enforced at every level in the inheritance chain via E05150.","keywords":["E05150","chain","contract","grandchild","grandparent","immutable","inheritance","multi-level","pure","purity","side-effect"],"primaryTopics":[],"typicalErrors":[{"error":"E05150","correct":"override sound() as pure","incorrect":"override sound()","explanation":"At every level of the inheritance chain, the pure contract must be maintained. The abstract Animal.sound is pure, so Dog.sound and Labrador.sound must also be pure. See ek9 -h E05150 for details."},{"error":"E50050","correct":"override describe() as pure","incorrect":"override describe() as pure\n        <- rtn as String: nonPureHelper()","explanation":"Even though describe is correctly declared as pure, calling a non-pure method from within it would violate the purity contract transitively. See ek9 -h E50050 for details."}],"companions":[]}
{"id":568,"category":"Purity Contracts","question":"How does purity apply to operators and their overrides?","url":"https://ek9.io/qa/QA0568.html","alternatePhrasings":["Can operators be pure in EK9?","Must I override a pure operator with a pure operator?","How does purity interact with operator overriding?"],"answer":"Operators follow the same purity rules as methods. If a parent defines a pure operator, the child override must also be pure (E05150). Many built-in operators are already pure.\n\nPURE OPERATORS\nComparison operators (<=>), equality (==, <>), hash (#?), and string ($) are typically pure. They compute a result without modifying state.\n\nOVERRIDE RULE\nIf a parent operator is pure, the child override must also be pure:\n  override operator <=> as pure\n    -> other as MyType\n    <- rtn as Integer: ...\n\nOPERATOR ? IS SPECIAL\nThe operator ? (isSet check) is inherited from the base type. Custom classes use 'override operator ?' and it is typically pure:\n  override operator ? as pure\n    <- rtn as Boolean: field?\n\nMUTATING OPERATORS\nOperators like +=, -=, :=:, :~:, :^: are mutating by nature and should NOT be pure. These modify the object in place.\n\nSee Q560 for pure method basics. See Q561 for purity in overrides. See Q245 for custom type operators. See Q241 for mutation operators.","ek9Example":"defines module qa.purity.operators\n\n  defines class\n\n    Temperature as open\n      degrees <- 0.0\n\n      Temperature()\n        -> degrees as Float\n        this.degrees: degrees\n\n      degrees() as pure\n        <- rtn as Float: degrees\n\n      //Pure comparison operator\n      operator <=> as pure\n        -> other as Temperature\n        <- rtn as Integer: degrees <=> other.degrees\n\n      //Pure equality derived from comparison\n      default operator ==\n\n      //Pure string representation\n      operator $ as pure\n        <- rtn as String: $degrees + \" degrees\"\n\n      //Pure hash code\n      operator #? as pure\n        <- rtn as Integer: #? degrees\n\n      //Pure isSet check\n      override operator ? as pure\n        <- rtn as Boolean: degrees?\n\n    //Child must keep operators pure\n    Celsius extends Temperature\n      Celsius()\n        -> degrees as Float\n        super(degrees)\n\n      //Override of pure operator must be pure\n      override operator $ as pure\n        <- rtn as String: `${degrees()} C`\n\n      default operator ?\n\n  defines program\n\n    PureOperatorsDemo()\n      stdout <- Stdout()\n\n      temp1 <- Temperature(20.0)\n      temp2 <- Temperature(30.0)\n      stdout.println(`${temp1} vs ${temp2}`)\n      stdout.println(`Equal: ${temp1 == temp2}`)\n      stdout.println(`Compare: ${temp1 <=> temp2}`)\n\n      cel <- Celsius(100.0)\n      stdout.println(`Celsius: ${cel}`)","migrationContext":"Java: operator overloading not supported, no purity concept. Kotlin: operator overloading exists but no purity enforcement. Rust: trait-based operators, no purity markers. EK9: operators follow identical purity rules to methods, E05150 applies to operator overrides.","keywords":["E05150","abstract","comparison","contract","equality","immutable","isSet","mutation","open","operator","override","pure","purity","side-effect","virtual"],"primaryTopics":[],"typicalErrors":[{"error":"E07500","correct":"override operator $ as pure","incorrect":"override operator $","explanation":"The parent Temperature.operator $ is pure. The child Celsius override must also be pure. Removing purity from the operator override violates the contract. See ek9 -h E07500 for details."},{"error":"E05120","correct":"override operator $ as pure","incorrect":"operator $ as pure","explanation":"When overriding a parent operator, the override keyword is required. Omitting it on operator $ in Celsius triggers shadowing detection. See ek9 -h E05120 for details."}],"companions":[]}
{"id":569,"category":"Purity Contracts","question":"How do traits interact with pure method contracts?","url":"https://ek9.io/qa/QA0569.html","alternatePhrasings":["Can a trait require pure methods?","Must a class implementing a trait maintain purity?","How does purity work with trait default methods?"],"answer":"Traits can declare methods as pure, and any class implementing the trait must maintain that purity contract. This is the same E05150 rule that applies to class inheritance.\n\nPURE METHODS IN TRAITS\nA trait can declare abstract pure methods:\n  defines trait\n    Measurable\n      measure() as pure abstract\n        <- rtn as Float?\nAny class implementing Measurable must provide a pure override.\n\nPURE DEFAULT METHODS\nTraits can have default method implementations that are pure:\n  Describable\n    label() as pure\n      <- rtn as String: \"unknown\"\nClasses can override the default but must keep it pure.\n\nIMPLEMENTATION RULE\nWhen a class uses 'with trait of' and overrides a pure trait method, the override must be pure (E05150):\n  MyClass with trait of Measurable\n    override measure() as pure\n      <- rtn as Float: 42.0\n\nMULTIPLE TRAITS\nIf a class implements multiple traits, each pure method contract is independently enforced. A method that satisfies a pure trait must be pure.\n\nSee Q106 for trait basics. See Q107 for multiple traits. See Q561 for purity override rules. See Q567 for purity chains.","ek9Example":"defines module qa.purity.traitpure\n\n  defines trait\n\n    //Trait requiring pure methods\n    Measurable\n      measure() as pure abstract\n        <- rtn as Float?\n\n    //Trait with pure default method\n    Labelable\n      label() as pure\n        <- rtn as String: \"unlabeled\"\n\n  defines class\n\n    //Must keep measure() pure because trait requires it\n    Box with trait of Measurable, Labelable\n      width <- 0.0\n      height <- 0.0\n\n      Box()\n        ->\n          width as Float\n          height as Float\n        this.width: width\n        this.height: height\n\n      //Pure override satisfies Measurable trait\n      override measure() as pure\n        <- rtn as Float: width * height\n\n      //Pure override satisfies Labelable trait\n      override label() as pure\n        <- rtn as String: `Box ${width}x${height}`\n\n      default operator ?\n\n    //Another implementation of same traits\n    Sphere with trait of Measurable, Labelable\n      radius <- 0.0\n\n      Sphere()\n        -> radius as Float\n        this.radius: radius\n\n      override measure() as pure\n        <- rtn as Float: 4.0 * 3.14159 * radius * radius\n\n      override label() as pure\n        <- rtn as String: `Sphere r=${radius}`\n\n      default operator ?\n\n  defines program\n\n    TraitPureMethodsDemo()\n      stdout <- Stdout()\n\n      box <- Box(3.0, 4.0)\n      stdout.println(`${box.label()}: ${box.measure()}`)\n\n      sphere <- Sphere(5.0)\n      stdout.println(`${sphere.label()}: ${sphere.measure()}`)\n\n      //Polymorphic use through trait\n      items <- List() of Measurable\n      items += Box(2.0, 3.0)\n      items += Sphere(1.0)\n\n      for item in items\n        stdout.println(`Measurement: ${item.measure()}`)","migrationContext":"Java: interfaces can have default methods but no purity concept. Kotlin: interfaces with defaults but no purity. Rust: traits with default implementations but no purity markers. EK9: traits can declare pure methods, implementations must maintain purity via E05150.","keywords":["E05150","abstract","contract","default","immutable","implement","method","pure","purity","side-effect","trait"],"primaryTopics":[],"typicalErrors":[{"error":"E05150","correct":"override measure() as pure","incorrect":"override measure()","explanation":"The Measurable trait declares measure as pure abstract. The implementing class must maintain purity. Overriding without as pure violates the trait contract. See ek9 -h E05150 for details."},{"error":"E05150","correct":"override label() as pure","incorrect":"override label()","explanation":"The Labelable trait provides a pure default for label. Overriding it without as pure would violate the purity contract inherited from the trait. See ek9 -h E05150 for details."}],"companions":[]}
{"id":570,"category":"Override Mechanics","question":"What does 'override' mean in EK9 and when is it required?","url":"https://ek9.io/qa/QA0570.html","alternatePhrasings":["How do I override a method in EK9?","When must I use the override keyword?","What is the override keyword in EK9?"],"answer":"The 'override' keyword is required whenever a child class replaces a method from a parent class or trait. EK9 requires explicit override declarations to prevent accidental shadowing.\n\nWHEN OVERRIDE IS REQUIRED\nWhenever a method in a child class has the same name and signature as a method in a parent class or implemented trait, you must use 'override'. Omitting it produces E05110.\n\nSYNTAX\nPlace 'override' before the method name:\n  override process()\n    -> text as String\n    <- rtn as String: text.upperCase()\n\nWHY EXPLICIT OVERRIDE\nPrevents accidental shadowing: if you accidentally match a parent method, the compiler catches it. Documents intent: readers know this method replaces parent behavior. Catches renames: if the parent renames a method, the override declaration breaks, alerting you.\n\nOVERRIDE WITH ABSTRACT\nImplementing an abstract method also requires 'override':\n  override calculate()\n    <- rtn as Integer: 42\n\nOVERRIDE WITH TRAITS\nImplementing or replacing a trait method requires 'override':\n  MyClass with trait of Printable\n    override display()\n      <- rtn as String: name\n\nSee Q571 for missing override keyword errors. See Q572 for false override claims. See Q573 for access modifier rules. See Q577 for operator override. See Q606 for method shadowing prevention.\nSee Q676 for override access hierarchy. See Q699 for return type covariance.","ek9Example":"defines module qa.override.basics\n\n  defines class\n\n    Vehicle as open\n      speed() as pure\n        <- rtn as String: \"unknown speed\"\n\n      describe() as pure\n        <- rtn as String: \"a vehicle\"\n\n      default operator ?\n\n    Car extends Vehicle as open\n      //Override is required: same name and signature as parent\n      override speed() as pure\n        <- rtn as String: \"fast\"\n\n      override describe() as pure\n        <- rtn as String: \"a car\"\n\n      default operator ?\n\n    ElectricCar extends Car\n      override speed() as pure\n        <- rtn as String: \"very fast\"\n\n      override describe() as pure\n        <- rtn as String: \"an electric car\"\n\n      default operator ?\n\n  defines program\n\n    OverrideBasicsDemo()\n      stdout <- Stdout()\n\n      vehicle <- Vehicle()\n      stdout.println(`Vehicle: ${vehicle.describe()}, ${vehicle.speed()}`)\n\n      car <- Car()\n      stdout.println(`Car: ${car.describe()}, ${car.speed()}`)\n\n      electric <- ElectricCar()\n      stdout.println(`Electric: ${electric.describe()}, ${electric.speed()}`)\n\n      //Polymorphic use\n      vehicles <- List() of Vehicle\n      vehicles += Vehicle()\n      vehicles += Car()\n      vehicles += ElectricCar()\n\n      for item in vehicles\n        stdout.println(`${item.describe()} goes ${item.speed()}`)","migrationContext":"Java: @Override annotation is optional but recommended. Python: no override concept. Rust: no method overriding (trait default implementations). Go: no inheritance. Kotlin: 'override' keyword is required (same as EK9). Swift: 'override' keyword required. C#: 'override' keyword required for virtual methods. EK9: 'override' keyword is mandatory, E05110 if missing.","keywords":["E05110","abstract","child","explicit","inherit","method","open","override","parent","require","virtual"],"primaryTopics":["override","method override"],"typicalErrors":[{"error":"E05120","correct":"override speed() as pure","incorrect":"speed() as pure","explanation":"When a child class has a method matching a parent method, the override keyword is required. Omitting it causes method shadowing detection. See ek9 -h E05120 for details."},{"error":"E50001","correct":"vehicle <- Vehicle()","incorrect":"vehicleXYZ <- Vehicle()","explanation":"Renaming the variable means later references to 'vehicle' become unresolved, triggering E50001. See ek9 -h E50001 for details."}],"companions":[]}
{"id":571,"category":"Override Mechanics","question":"What happens if I forget the 'override' keyword?","url":"https://ek9.io/qa/QA0571.html","alternatePhrasings":["What is E05110 does not override?","Why does the compiler reject my method that matches the parent?","How do I fix E05110 missing override?"],"answer":"If your method has the same name and signature as a parent method but you omit the 'override' keyword, the compiler reports E05110. This prevents accidental method shadowing.\n\nTHE ERROR: E05110\nE05110 fires when a method matches a parent method exactly but lacks the 'override' keyword. The compiler refuses to silently shadow the parent behavior.\n\nFIX: ADD OVERRIDE\nSimply add 'override' before the method name:\n  //Before (error):\n  process()\n    <- rtn as String: \"child\"\n  //After (correct):\n  override process()\n    <- rtn as String: \"child\"\n\nALTERNATIVE: RENAME\nIf you intended a new method (not an override), rename it to avoid matching the parent signature.\n\nWHY EXPLICIT OVERRIDE MATTERS\nIn large codebases, methods can accidentally match parent signatures after refactoring. Without mandatory override, this creates subtle bugs where the child silently replaces parent behavior. EK9 catches this at compile time.\n\nSee Q570 for override basics. See Q572 for the opposite: claiming override when nothing matches. See Q105 for method dispatch.","ek9Example":"defines module qa.override.missingkeyword\n\n  defines class\n\n    Greeter as open\n      greet() as pure\n        -> recipient as String\n        <- rtn as String: \"Hello, \" + recipient\n\n      farewell() as pure\n        -> recipient as String\n        <- rtn as String: \"Goodbye, \" + recipient\n\n      default operator ?\n\n    //Correct: override keyword is present\n    FormalGreeter extends Greeter\n      override greet() as pure\n        -> recipient as String\n        <- rtn as String: \"Good day, \" + recipient\n\n      override farewell() as pure\n        -> recipient as String\n        <- rtn as String: \"Farewell, \" + recipient\n\n      //New method: no override needed (different name)\n      bow() as pure\n        -> recipient as String\n        <- rtn as String: \"Bowing to \" + recipient\n\n      default operator ?\n\n  defines program\n\n    MissingOverrideDemo()\n      stdout <- Stdout()\n\n      recipientName <- \"Alice\"\n      greeter <- FormalGreeter()\n      stdout.println(greeter.greet(recipientName))\n      stdout.println(greeter.farewell(recipientName))\n      stdout.println(greeter.bow(recipientName))","migrationContext":"Java: @Override is optional, shadowing is silent without it. Python: no override mechanism. Kotlin: override is mandatory (same as EK9). C#: override keyword required for virtual methods. EK9: override is mandatory, E05110 reported if missing.","keywords":["E05110","abstract","explicit","inherit","keyword","match","missing","open","override","parent","shadow","virtual"],"primaryTopics":[],"typicalErrors":[{"error":"E05120","correct":"override greet() as pure","incorrect":"greet() as pure","explanation":"When a method matches a parent method signature, omitting override causes a shadowing error. The compiler requires explicit intent. See ek9 -h E05120 for details."},{"error":"E05120","correct":"override farewell() as pure","incorrect":"farewell() as pure","explanation":"Claiming override on bow() would be a false claim since no parent method named bow exists. The override keyword must match an actual parent method. See ek9 -h E02030 for details."}],"companions":[]}
{"id":572,"category":"Override Mechanics","question":"What happens if I claim 'override' but no parent method matches?","url":"https://ek9.io/qa/QA0572.html","alternatePhrasings":["What is E05100 override inappropriate?","Why does the compiler say my override has nothing to override?","How do I fix a false override claim?"],"answer":"Note: override applies to both methods and operators (e.g., override operator ? as pure, override operator $ as pure). \n\nIf you mark a method with 'override' but no parent class or trait has a matching method, the compiler reports E05100. This catches typos and stale overrides.\n\nTHE ERROR: E05100\nE05100 fires when 'override' is used but no matching method exists in any parent. Common causes: method name typo, parent method was renamed or removed, wrong parent class.\n\nFIX: REMOVE OVERRIDE\nIf this is a new method, remove the 'override' keyword:\n  //Before (error):\n  override newMethod()\n    <- rtn as String: \"new\"\n  //After (correct):\n  newMethod()\n    <- rtn as String: \"new\"\n\nFIX: CORRECT THE NAME\nIf you intended to override, fix the method name to match the parent:\n  //Before (error, typo):\n  override procss()\n  //After (correct):\n  override process()\n\nFIX: CHECK SIGNATURES\nThe override must match the parent signature exactly. Different parameter types mean no match.\n\nSAFETY NET\nE05100 is a safety net against stale code. When a parent method is renamed, all child overrides immediately break with E05100, forcing you to update them.\n\nSee Q570 for override basics. See Q571 for the opposite: missing override keyword. See Q573 for access modifier rules.","ek9Example":"defines module qa.override.falseclaim\n\n  defines class\n\n    Renderer as open\n      render() as pure\n        -> content as String\n        <- rtn as String: content\n\n      default operator ?\n\n    //Correct: override matches parent method exactly\n    HtmlRenderer extends Renderer\n      override render() as pure\n        -> content as String\n        <- rtn as String: `<p>${content}</p>`\n\n      //New method: no override keyword (no parent match)\n      renderWithTag() as pure\n        ->\n          content as String\n          tag as String\n        <- rtn as String: `<${tag}>${content}</${tag}>`\n\n      default operator ?\n\n  defines program\n\n    FalseOverrideClaimDemo()\n      stdout <- Stdout()\n\n      renderer <- HtmlRenderer()\n      stdout.println(renderer.render(\"hello\"))\n      stdout.println(renderer.renderWithTag(\"hello\", \"div\"))","migrationContext":"Java: @Override produces compile error if nothing to override. Kotlin: override keyword causes error if nothing matches. C#: override without matching virtual method is an error. EK9: E05100 when override has no matching parent method.","keywords":["E05100","abstract","claim","false","function","inappropriate","inherit","mismatch","open","override","parent","typo","virtual"],"primaryTopics":[],"typicalErrors":[{"error":"E05110","correct":"renderWithTag() as pure","incorrect":"override renderWithTag() as pure","explanation":"Adding override to renderWithTag would be a false claim since Renderer has no method named renderWithTag. The compiler rejects override when no parent method matches. See ek9 -h E05110 for details."},{"error":"E05120","correct":"override render() as pure","incorrect":"render() as pure","explanation":"Omitting override on the render method in HtmlRenderer would be detected as method shadowing since the parent Renderer already defines render. See ek9 -h E05120 for details."}],"companions":[]}
{"id":573,"category":"Override Mechanics","question":"What are the access modifier rules for method overrides?","url":"https://ek9.io/qa/QA0573.html","alternatePhrasings":["What is E05130 method access modifiers differ?","Can I change the visibility of an overridden method?","Can I make an override more or less restrictive?"],"answer":"Note: override applies to both methods and operators (e.g., override operator ? as pure, override operator $ as pure). \n\nWhen overriding a method, the access modifier must be at least as permissive as the parent. You cannot make an override more restrictive. The compiler reports E05130 for violations.\n\nTHE RULE\nPublic parent method requires public override (same level). Protected parent method allows protected or public override (same or wider). Private methods cannot be overridden (not visible to children).\n\nTHE ERROR: E05130\nE05130 fires when an override is more restrictive than the parent. For example, making a public parent method protected or private in the child.\n\nWHY THIS RULE\nLiskov Substitution Principle: if code calls parent.method(), it expects child.method() to be equally accessible. Reducing access would break polymorphic code.\n\nDEFAULT ACCESS\nIn EK9, methods without an explicit modifier are public. Use 'private' or 'protected' to restrict access.\n\nNO PRIVATE OVERRIDE\nPrivate methods are not inherited and cannot be overridden. They are invisible to child classes.\n\nSee Q570 for override basics. See Q572 for false override claims. See Q95 for field visibility.","ek9Example":"defines module qa.override.accessmodifiers\n\n  defines constant\n\n    MIN_ENHANCED_LENGTH <- 2\n\n  defines class\n\n    Service as open\n      //Public method (default)\n      process() as pure\n        -> request as String\n        <- rtn as String: \"processed: \" + request\n\n      //Protected method\n      protected validate() as pure\n        -> input as String\n        <- rtn as Boolean: length input > 0\n\n      default operator ?\n\n    //Correct: public override of public method\n    EnhancedService extends Service\n      override process() as pure\n        -> request as String\n        <- rtn as String: \"enhanced: \" + request\n\n      //Correct: can widen protected to public\n      //Or keep it protected (both are valid)\n      override protected validate() as pure\n        -> input as String\n        <- rtn as Boolean: length input > MIN_ENHANCED_LENGTH\n\n      default operator ?\n\n  defines program\n\n    AccessModifierRulesDemo()\n      stdout <- Stdout()\n\n      svc <- EnhancedService()\n      stdout.println(svc.process(\"hello\"))","migrationContext":"Java: same rule, cannot reduce visibility in override. Kotlin: same rule, override cannot be more restrictive. C#: same rule for virtual methods. EK9: E05130 enforces access modifier consistency in overrides.","keywords":["E05130","abstract","access","function","inherit","modifier","open","override","private","protected","public","restrictive","virtual","visibility"],"primaryTopics":[],"typicalErrors":[{"error":"E07010","correct":"override process() as pure","incorrect":"override private process() as pure","explanation":"Making an override more restrictive than the parent is forbidden. The parent process() is public, so a private override would violate the Liskov Substitution Principle. See ek9 -h E07010 for details."},{"error":"E07010","correct":"override protected validate() as pure","incorrect":"override private validate() as pure","explanation":"Narrowing a protected parent method to private in the child violates access modifier rules. The override must be at least as permissive as the parent. See ek9 -h E07010 for details."}],"companions":[]}
{"id":574,"category":"Override Mechanics","question":"What happens if I try to extend or override in a closed type?","url":"https://ek9.io/qa/QA0574.html","alternatePhrasings":["What is E05100 override inappropriate for closed types?","Can I override methods if the parent is not 'as open'?","Why can't I extend a closed class?"],"answer":"EK9 types are closed by default. You can only extend a class that is declared 'as open' or 'as abstract'. Attempting to extend a closed class produces E05030. Since you cannot extend a closed class, you cannot override its methods.\n\nCLOSED BY DEFAULT\nEvery class without 'as open' or 'as abstract' is closed:\n  FinalProcessor\n    process() ...\nNo class can extend FinalProcessor.\n\nOPEN FOR EXTENSION\nAdd 'as open' to allow extension:\n  ProcessorBase as open\n    process() ...\nNow children can extend and override.\n\nABSTRACT IS ALWAYS OPEN\nAbstract classes must be extended (they are incomplete), so they are always open:\n  AbstractProcessor as abstract\n    process() as abstract ...\n\nWHY CLOSED BY DEFAULT\nPrevents fragile base class problems. Reduces the surface area for bugs. Forces deliberate design for extension. Follows modern language trends (Kotlin, Swift).\n\nSee Q101 for why types are closed by default. See Q102 for 'as open'. See Q103 for abstract classes. See Q570 for override basics.","ek9Example":"defines module qa.override.closedtype\n\n  defines constant\n\n    MIN_STRICT_LENGTH <- 3\n\n  defines class\n\n    //Closed class: cannot be extended\n    ClosedValidator\n      validate() as pure\n        -> input as String\n        <- rtn as Boolean: length input > 0\n\n      default operator ?\n\n    //Open class: can be extended and methods overridden\n    OpenValidator as open\n      validate() as pure\n        -> input as String\n        <- rtn as Boolean: length input > 0\n\n      default operator ?\n\n    //Extending the open class is allowed\n    StrictValidator extends OpenValidator\n      override validate() as pure\n        -> input as String\n        <- rtn as Boolean: length input > MIN_STRICT_LENGTH\n\n      default operator ?\n\n    //Abstract class: always open\n    AbstractValidator as abstract\n      check() as pure abstract\n        -> input as String\n        <- rtn as Boolean?\n\n      default operator ?\n\n    //Must extend abstract to use it\n    EmailValidator extends AbstractValidator\n      override check() as pure\n        -> input as String\n        <- rtn as Boolean: input contains \"@\"\n\n      default operator ?\n\n  defines program\n\n    ClosedTypeDemo()\n      stdout <- Stdout()\n\n      //Closed class used directly\n      closed <- ClosedValidator()\n      stdout.println(`Closed: ${ closed.validate(\"hi\")}`)\n\n      //Open class hierarchy\n      strict <- StrictValidator()\n      stdout.println(`Strict: ${strict.validate(\"hi\")}`)\n      stdout.println(`Strict: ${strict.validate(\"hello\")}`)\n\n      //Abstract class hierarchy\n      emailCheck <- EmailValidator()\n      stdout.println(`Email: ${emailCheck.check(\"user@host\")}`)\n      stdout.println(`Email: ${emailCheck.check(\"invalid\")}`)","migrationContext":"Java: all classes open by default, 'final' to close. Python: all classes open. Kotlin: classes closed by default, 'open' to allow extension. Swift: classes closed by default, 'open' to allow overriding. EK9: closed by default, 'as open' to allow extension.","keywords":["E05030","abstract","closed","default","extend","final","inherit","migrate","open","override","virtual"],"primaryTopics":[],"typicalErrors":[{"error":"E05030","correct":"StrictValidator extends OpenValidator","incorrect":"StrictValidator extends ClosedValidator","explanation":"ClosedValidator is not declared as open, so no class can extend it. EK9 types are closed by default. See ek9 -h E05030 for details."},{"error":"E05120","correct":"override validate() as pure","incorrect":"validate() as pure","explanation":"When overriding the validate method from OpenValidator, the override keyword is mandatory. Omitting it causes a shadowing error. See ek9 -h E05120 for details."}],"companions":[]}
{"id":575,"category":"Override Mechanics","question":"How do overrides work in deep class hierarchies?","url":"https://ek9.io/qa/QA0575.html","alternatePhrasings":["Does override apply to grandparent methods?","How does a three-level override chain work?","Must each level use 'override' in a deep hierarchy?"],"answer":"In a deep hierarchy (grandparent, parent, child), each level that replaces a method must use 'override'. The keyword is required at every level that provides a new implementation.\n\nOVERRIDE WORKS ON BOTH METHODS AND OPERATORS\nThe override keyword applies to methods AND operators:\n  override describe() as pure      method override\n  override operator ? as pure      operator override\nBoth follow the same rule: if the parent defines it, you must say override.\n\nTHREE-LEVEL PATTERN\nGrandparent declares the method. Parent overrides it (must use 'override'). Grandchild overrides again (must use 'override'). Each level independently declares its intent to replace.\n\nEVERY LEVEL NEEDS OVERRIDE\nEven though grandparent is the original, and parent already overrides, the grandchild must ALSO say 'override'. There is no implicit inheritance of the override status. This applies equally to methods and operators.\n\nSUPER ACCESS\nEach level can access the immediate parent with 'super':\n  override describe()\n    parentDesc <- super.describe()\n    <- rtn as String: parentDesc + \" + child\"\n\nSKIPPING LEVELS\nIf the parent does NOT override (keeps grandparent behavior), the grandchild can still override the grandparent method. The override applies to whatever implementation was last provided.\n\nSee Q570 for override basics. See Q567 for purity through chains. See Q102 for 'as open'. ","ek9Example":"defines module qa.override.deepchain\n\n  defines class\n\n    //Level 1: grandparent — operator ? needs override (inherited from base)\n    Transport as open\n      describe() as pure\n        <- rtn as String: \"transport\"\n\n      capacity() as pure\n        <- rtn as Integer: 0\n\n      //override on operator — same keyword as methods\n      override operator ? as pure\n        <- rtn as Boolean: true\n\n      operator $ as pure\n        <- rtn as String: `Transport: ${describe()}`\n\n    //Level 2: parent overrides methods AND operators\n    LandTransport extends Transport as open\n      override describe() as pure\n        <- rtn as String: \"land transport\"\n\n      override capacity() as pure\n        <- rtn as Integer: 4\n\n      //override on operator $ — parent defined it, so override required\n      override operator $ as pure\n        <- rtn as String: `Land: ${describe()}`\n\n    //Level 3: grandchild overrides again — methods and operators\n    Truck extends LandTransport\n      override describe() as pure\n        <- rtn as String: \"truck\"\n\n      override capacity() as pure\n        <- rtn as Integer: 20\n\n      override operator $ as pure\n        <- rtn as String: `Truck: ${describe()}`\n\n    //Another branch at level 3\n    Motorcycle extends LandTransport\n      override describe() as pure\n        <- rtn as String: \"motorcycle\"\n\n      override capacity() as pure\n        <- rtn as Integer: 2\n\n      override operator $ as pure\n        <- rtn as String: `Bike: ${describe()}`\n\n  defines program\n\n    DeepOverrideChainDemo()\n      stdout <- Stdout()\n\n      //Each level has its own implementation\n      items <- List() of Transport\n      items += Transport()\n      items += LandTransport()\n      items += Truck()\n      items += Motorcycle()\n\n      for item in items\n        stdout.println(`${item.describe()}: capacity ${item.capacity()}`)","migrationContext":"Java: @Override at each level is recommended but optional. Kotlin: override at each level is mandatory. C#: override at each level is required. EK9: override is mandatory at every level in the chain.","keywords":["abstract","chain","child","class","deep","grandparent","hierarchy","inherit","level","open","override","parent","virtual"],"primaryTopics":[],"typicalErrors":[{"error":"E05120","correct":"override describe() as pure","incorrect":"describe() as pure","explanation":"At every level of the hierarchy, the override keyword is required when replacing a parent method. Omitting it at any level triggers shadowing detection. See ek9 -h E05120 for details."},{"error":"E05030","correct":"LandTransport extends Transport as open","incorrect":"LandTransport extends Truck as open","explanation":"If LandTransport extended Truck which extends LandTransport, a circular hierarchy would be created. The compiler detects cycles at any depth. See ek9 -h E05030 for details."}],"companions":[]}
{"id":576,"category":"Override Mechanics","question":"How do overrides work when a class implements a trait and extends a parent?","url":"https://ek9.io/qa/QA0576.html","alternatePhrasings":["What happens when a method comes from both a parent class and a trait?","How does EK9 resolve overrides from class and trait sources?","Can I override methods from both parent and trait?"],"answer":"A class can extend a parent AND implement traits. When the same method appears in both, the class must provide an override that satisfies both contracts.\n\nBOTH SOURCES\nIf a parent class has method X and a trait also declares method X with the same signature, the child class must override it once. The single override satisfies both.\n\nTRAIT-ONLY METHODS\nMethods that exist only in the trait (not in the parent) are overridden with 'override' just like any other inherited method.\n\nPARENT-ONLY METHODS\nMethods that exist only in the parent are overridden with 'override' in the normal way.\n\nPURITY FROM EITHER SOURCE\nIf either the parent method or the trait method is pure, the override must be pure. The strictest contract wins.\n\nSee Q106 for trait basics. See Q107 for multiple traits. See Q570 for override basics. See Q578 for open, abstract, and override relationships.","ek9Example":"defines module qa.override.classandtrait\n\n  defines trait\n\n    Printable\n      display() as pure abstract\n        <- rtn as String?\n\n    Sizeable\n      size() as pure abstract\n        <- rtn as Integer?\n\n  defines class\n\n    Container as open\n      describe() as pure\n        <- rtn as String: \"container\"\n\n      default operator ?\n\n    //Extends parent AND implements traits\n    NamedContainer extends Container with trait of Printable, Sizeable\n      containerName <- String()\n      itemCount <- 0\n\n      NamedContainer()\n        ->\n          containerName as String\n          itemCount as Integer\n        this.containerName: containerName\n        this.itemCount: itemCount\n\n      //Override from parent class\n      override describe() as pure\n        <- rtn as String: \"named container: \" + containerName\n\n      //Override from Printable trait\n      override display() as pure\n        <- rtn as String: `${containerName} (${itemCount} items)`\n\n      //Override from Sizeable trait\n      override size() as pure\n        <- rtn as Integer: itemCount\n\n      default operator ?\n\n  defines program\n\n    ClassAndTraitOverrideDemo()\n      stdout <- Stdout()\n\n      nc <- NamedContainer(\"toolbox\", 15)\n\n      //Using through parent type\n      container <- nc\n      stdout.println(container.describe())\n\n      //Using through trait types\n      printable <- nc\n      stdout.println(printable.display())\n\n      sizeable <- nc\n      stdout.println(`Size: ${sizeable.size()}`)","migrationContext":"Java: class extends + interface implements, @Override for both. Kotlin: class extends + interface implements, override required. EK9: class extends + 'with trait of', override required for both sources.","keywords":["abstract","both","class","contract","implement","inherit","open","override","satisfy","trait","virtual"],"primaryTopics":[],"typicalErrors":[{"error":"E05120","correct":"override display() as pure","incorrect":"display() as pure","explanation":"When implementing a trait method, the override keyword is required. Omitting it on display from the Printable trait triggers shadowing detection. See ek9 -h E05120 for details."},{"error":"E05110","correct":"override describe() as pure","incorrect":"override render() as pure","explanation":"Claiming override on a method name that does not exist in the parent class or any implemented trait is a false override claim. See ek9 -h E05110 for details."}],"companions":[],"oracleToolHint":{"tool":"ek9_implement","intent":"trait","description":"Oracle can generate override method stubs when a class extends a parent and implements a trait."}}
{"id":577,"category":"Override Mechanics","question":"How do I override operators in EK9?","url":"https://ek9.io/qa/QA0577.html","alternatePhrasings":["How do I override operator ? in a child class?","What is 'override operator' syntax?","Must I use 'override' for operators too?"],"answer":"Operators follow the same override rules as methods. When a parent class defines an operator, the child must use 'override operator' to replace it.\n\nOVERRIDE OPERATOR ? (ISSET)\nThe most commonly overridden operator is '?'. Since all types inherit operator ? from the base, custom classes use 'override operator ?':\n  override operator ? as pure\n    <- rtn as Boolean: name?\n\nOVERRIDE COMPARISON\nIf a parent defines operator <=>, the child uses 'override operator <=>':\n  override operator <=> as pure\n    -> other as MyType\n    <- rtn as Integer: ...\n\nOVERRIDE STRING\nIf a parent defines operator $, the child overrides it:\n  override operator $ as pure\n    <- rtn as String: ...\n\nDEFAULT OPERATOR\nThe 'default operator ?' shorthand can be used in any class. When a parent already has operator ?, using 'default operator ?' in the child effectively overrides it with the default implementation.\n\nSee Q570 for override basics. See Q245 for custom type operators. See Q568 for pure operators. See Q96 for class operators.","ek9Example":"defines module qa.override.operator\n\n  defines class\n\n    Measurement as open\n      amount <- 0.0\n\n      Measurement()\n        -> amount as Float\n        this.amount: amount\n\n      amount() as pure\n        <- rtn as Float: amount\n\n      operator <=> as pure\n        -> other as Measurement\n        <- rtn as Integer: amount <=> other.amount\n\n      default operator ==\n\n      operator $ as pure\n        <- rtn as String: $amount\n\n      operator #? as pure\n        <- rtn as Integer: #? amount\n\n      override operator ? as pure\n        <- rtn as Boolean: amount?\n\n    //Child overrides operators\n    Weight extends Measurement\n      Weight()\n        -> amount as Float\n        super(amount)\n\n      //Override string representation\n      override operator $ as pure\n        <- rtn as String: `${amount()} kg`\n\n      default operator ?\n\n    Length extends Measurement\n      Length()\n        -> amount as Float\n        super(amount)\n\n      override operator $ as pure\n        <- rtn as String: `${amount()} m`\n\n      default operator ?\n\n  defines program\n\n    OverrideOperatorDemo()\n      stdout <- Stdout()\n\n      weight <- Weight(75.5)\n      stdout.println(`Weight: ${weight}`)\n\n      lengthVal <- Length(1.82)\n      stdout.println(`Length: ${lengthVal}`)\n\n      //Comparison works through parent operator\n      stdout.println(`Compare: ${weight <=> Weight(80.0)}`)\n      stdout.println(`Equal: ${weight == Weight(75.5)}`)","migrationContext":"Java: no operator overloading. Kotlin: operator overloading exists, override keyword applies. C#: operator overloading but no inheritance override. EK9: operators follow identical override rules to methods, 'override operator' syntax.","keywords":["abstract","child","comparison","default","inherit","isSet","open","operator","override","string","virtual"],"primaryTopics":[],"typicalErrors":[{"error":"E05120","correct":"override operator $ as pure","incorrect":"operator $ as pure","explanation":"When the parent class defines operator $, the child must use override operator $ to replace it. Omitting override on operator overrides triggers shadowing detection. See ek9 -h E05120 for details."},{"error":"E07500","correct":"override operator $ as pure","incorrect":"override operator $","explanation":"The parent operator $ is pure, so the child override must also be pure. Removing purity from the override violates the purity contract. See ek9 -h E07500 for details."}],"companions":[]}
{"id":578,"category":"Override Mechanics","question":"What is the relationship between open, abstract, and override?","url":"https://ek9.io/qa/QA0578.html","alternatePhrasings":["How do open and abstract interact with override?","When is a method overridable in EK9?","How do I design a class hierarchy for extension?"],"answer":"In EK9, three modifiers control method overriding: 'as open' makes a class extensible, 'as abstract' makes a class extensible with incomplete methods, and 'override' declares intent to replace.\n\nCLOSED (DEFAULT)\nClasses without 'as open' or 'as abstract' cannot be extended. Their methods cannot be overridden.\n\nAS OPEN\nMakes a class extensible. All public and protected methods become overridable in children:\n  Widget as open\n    draw() ...\nChildren can override draw() with 'override draw()'.\n\nAS ABSTRACT\nMakes a class extensible with some methods having no body:\n  Shape as abstract\n    area() as abstract\n      <- rtn as Float?\nChildren MUST override abstract methods.\n\nOVERRIDE CHAINS\nA child that is 'as open' allows further overriding:\n  Base as open -> Child extends Base as open -> GrandChild extends Child\nEach level can override.\n\nA child WITHOUT 'as open' stops the chain:\n  Base as open -> FinalChild extends Base\nFinalChild's methods cannot be overridden further.\n\nSee Q101 for closed by default. See Q102 for 'as open'. See Q103 for abstract classes. See Q570 for override basics. See Q575 for deep override chains.","ek9Example":"defines module qa.override.openabstract\n\n  defines class\n\n    //Abstract: must be extended, abstract methods must be overridden\n    Shape as abstract\n      area() as pure abstract\n        <- rtn as Float?\n\n      perimeter() as pure abstract\n        <- rtn as Float?\n\n      default operator ?\n\n    //Open: can be extended further\n    Rectangle extends Shape as open\n      rectWidth <- 0.0\n      rectHeight <- 0.0\n\n      Rectangle()\n        ->\n          rectWidth as Float\n          rectHeight as Float\n        this.rectWidth: rectWidth\n        this.rectHeight: rectHeight\n\n      override area() as pure\n        <- rtn as Float: rectWidth * rectHeight\n\n      override perimeter() as pure\n        <- rtn as Float: 2.0 * (rectWidth + rectHeight)\n\n      rectWidth() as pure\n        <- rtn as Float: rectWidth\n\n      rectHeight() as pure\n        <- rtn as Float: rectHeight\n\n      default operator ?\n\n    //Closed (default): cannot be extended further\n    Square extends Rectangle\n      Square()\n        -> side as Float\n        super(side, side)\n\n      //Can override parent methods\n      override area() as pure\n        <- rtn as Float: rectWidth() * rectWidth()\n\n      default operator ?\n\n  defines program\n\n    OpenAbstractOverrideDemo()\n      stdout <- Stdout()\n\n      square <- Square(5.0)\n      stdout.println(`Square area: ${square.area()}`)\n      stdout.println(`Square perimeter: ${square.perimeter()}`)\n\n      //Polymorphic use\n      shapes <- List() of Shape\n      shapes += Rectangle(3.0, 4.0)\n      shapes += Square(6.0)\n\n      for shape in shapes\n        stdout.println(`Area: ${shape.area()}, Perimeter: ${shape.perimeter()}`)","migrationContext":"Java: classes open by default, final to close. Kotlin: classes and methods closed by default, open keyword on both. Swift: class and method level open/final control. EK9: class-level open/abstract, all public methods overridable in open classes.","keywords":["abstract","chain","closed","design","extensible","hierarchy","inherit","open","override","virtual"],"primaryTopics":[],"typicalErrors":[{"error":"E50060","correct":"Square extends Rectangle","incorrect":"Square extends Shape","explanation":"While extending Shape directly is not circular, Square is designed to reuse Rectangle. If Square tried to extend a type that creates a cycle, E50060 would fire. See ek9 -h E50060 for details."},{"error":"E05120","correct":"override area() as pure","incorrect":"area() as pure","explanation":"When overriding the abstract area method from Shape, the override keyword is mandatory. Without it, the compiler detects method shadowing. See ek9 -h E05120 for details."},{"error":"E05150","correct":"override area() as pure","incorrect":"override area()","explanation":"The abstract area method in Shape is declared as pure, so every override must also be pure. Removing purity from the override violates the contract. See ek9 -h E05150 for details."}],"companions":[]}
{"id":579,"category":"Override Mechanics","question":"How do overrides work in a three-level trait hierarchy?","url":"https://ek9.io/qa/QA0579.html","alternatePhrasings":["Can traits extend other traits with overrides?","How does override work when traits inherit from traits?","What happens with override in a trait chain?"],"answer":"Note: override applies to both methods and operators (e.g., override operator ? as pure, override operator $ as pure). \n\nTraits can extend other traits, creating trait inheritance chains. Each level can provide default implementations or declare abstract methods. Classes implementing the final trait must override any remaining abstract methods.\n\nTRAIT EXTENDING TRAIT\nA trait can extend another trait using 'is' or 'extends':\n  BaseTrait\n    method() as abstract ...\n  ExtendedTrait is BaseTrait\n    override method() ...\n    extraMethod() ...\n\nTHREE-LEVEL CHAIN\nBaseTrait declares abstract methods. MiddleTrait extends BaseTrait and provides defaults. FinalTrait extends MiddleTrait and can override defaults. Classes implement any trait in the chain.\n\nOVERRIDE AT EACH LEVEL\nEach trait level that replaces an inherited method must use 'override'. This follows the same rules as class overrides.\n\nCLASS IMPLEMENTATION\nA class implementing the final trait must override any remaining abstract methods. Methods with defaults can optionally be overridden.\n\nSee Q106 for trait basics. See Q107 for multiple traits. See Q570 for override basics. See Q576 for class and trait override.\nSee Q676 for override access hierarchy. See Q680 for diamond trait resolution.","ek9Example":"defines module qa.override.traitchain\n\n  defines trait\n\n    //Level 1: base trait\n    Identifiable\n      identity() as pure abstract\n        <- rtn as String?\n\n    //Level 2: extends base, adds methods\n    Displayable is Identifiable\n      format() as pure\n        <- rtn as String: `[${identity()}]`\n\n    //Level 3: extends middle, can override defaults\n    Loggable is Displayable\n      override format() as pure\n        <- rtn as String: \"LOG:\" + identity()\n\n      severity() as pure\n        <- rtn as String: \"INFO\"\n\n  defines class\n\n    //Implements final trait: must override remaining abstract\n    LogEntry with trait of Loggable\n      message <- String()\n\n      LogEntry()\n        -> message as String\n        this.message: message\n\n      //Must override: abstract from Identifiable\n      override identity() as pure\n        <- rtn as String: message\n\n      //Can optionally override: default from Loggable\n      override severity() as pure\n        <- rtn as String: \"WARN\"\n\n      default operator ?\n\n    //Another implementation keeping defaults\n    AuditEntry with trait of Loggable\n      action <- String()\n\n      AuditEntry()\n        -> action as String\n        this.action: action\n\n      //Must override abstract from Identifiable\n      override identity() as pure\n        <- rtn as String: action\n\n      //Keeps default severity (\"INFO\") and format (\"LOG:...\")\n      default operator ?\n\n  defines program\n\n    ThreeLevelTraitChainDemo()\n      stdout <- Stdout()\n\n      logItem <- LogEntry(\"disk full\")\n      stdout.println(`${logItem.format()} [${logItem.severity()}]`)\n\n      auditItem <- AuditEntry(\"user login\")\n      stdout.println(`${auditItem.format()} [${auditItem.severity()}]`)","migrationContext":"Java: interfaces can extend other interfaces, default methods since Java 8. Kotlin: interfaces can extend interfaces with defaults. Rust: trait inheritance with supertraits. EK9: trait extending trait with 'is' or 'extends', override required at each level.","keywords":["abstract","chain","default","extend","hierarchy","inherit","open","override","three-level","trait","virtual"],"primaryTopics":[],"typicalErrors":[{"error":"E05120","correct":"override identity() as pure","incorrect":"identity() as pure","explanation":"Implementing the abstract identity method from Identifiable requires the override keyword. Omitting it triggers shadowing detection. See ek9 -h E05120 for details."},{"error":"E05110","correct":"override severity() as pure","incorrect":"override logging() as pure","explanation":"Claiming override on a method name that does not exist in any trait in the chain is a false override claim. Only methods defined in the trait hierarchy can be overridden. See ek9 -h E05110 for details."}],"companions":[]}
{"id":580,"category":"Constructor Delegation","question":"How do I call one constructor from another using this()?","url":"https://ek9.io/qa/QA0580.html","alternatePhrasings":["How does constructor chaining work in EK9?","How do I delegate between constructors with this()?","What is this() in a constructor?"],"answer":"Use this() as the first statement in a constructor to delegate to another constructor of the same class. This avoids duplicating initialization logic.\n\nBASIC DELEGATION\nCall this() with arguments matching another constructor:\n  Account()\n    -> name as String\n    this(name, 0.0)  //delegates to two-arg constructor\n  Account()\n    -> name as String, balance as Float\n    this.name: name\n    this.balance: balance\n\nMUST BE FIRST STATEMENT\nthis() must be the very first statement in the constructor body. No code can execute before delegation (E05050).\n\nCHAINING MULTIPLE\nConstructors can chain through multiple levels:\n  Account() -> this(\"anonymous\") -> this(\"anonymous\", 0.0)\nEach level delegates to a more specific constructor.\n\nNO CIRCULAR DELEGATION\nConstructor chains must terminate. A circular chain (A calls B calls A) is a compile error.\n\nSee Q581 for super() delegation. See Q582 for delegation order rules. See Q583 for this() vs super() restrictions. See Q94 for constructor basics. See Q605 for why 'this' is invalid in standalone functions.","ek9Example":"defines module qa.constructor.thisdelegation\n\n  defines class\n\n    Account\n      holder <- String()\n      balance <- 0.0\n      accountType <- String()\n\n      //One-arg delegates to two-arg\n      Account()\n        -> holder as String\n        this(holder, 0.0)\n\n      //Two-arg delegates to three-arg\n      Account()\n        ->\n          holder as String\n          balance as Float\n        this(holder, balance, \"standard\")\n\n      //Three-arg: does the actual initialization\n      Account()\n        ->\n          holder as String\n          balance as Float\n          accountType as String\n        this.holder: holder\n        this.balance: balance\n        this.accountType: accountType\n\n      holder() as pure\n        <- rtn as String: holder\n\n      balance() as pure\n        <- rtn as Float: balance\n\n      accountType() as pure\n        <- rtn as String: accountType\n\n      default operator ?\n\n  defines program\n\n    ThisDelegationDemo()\n      stdout <- Stdout()\n\n      //Uses one-arg constructor (delegates through chain)\n      acc1 <- Account(\"Alice\")\n      stdout.println(`${acc1.holder()}: ${acc1.balance()} (${acc1.accountType()})`)\n\n      //Uses two-arg constructor\n      acc2 <- Account(\"Bob\", 100.0)\n      stdout.println(`${acc2.holder()}: ${acc2.balance()} (${acc2.accountType()})`)\n\n      //Uses three-arg constructor directly\n      acc3 <- Account(\"Charlie\", 500.0, \"premium\")\n      stdout.println(`${acc3.holder()}: ${acc3.balance()} (${acc3.accountType()})`)","migrationContext":"Java: this() constructor chaining, must be first statement. Python: no this() equivalent. Kotlin: primary constructor + this() delegation. C#: this() chaining, must be first. EK9: this() delegation with first-statement rule, E05050 if not first.","keywords":["E05050","chain","constant","constructor","delegation","first","statement","super","this"],"primaryTopics":["constructor delegation","this delegation"],"typicalErrors":[{"error":"E05050","correct":"Account()\n        -> holder as String\n        this(holder, 0.0)","incorrect":"Account()\n        -> holder as String\n        this.holder: holder\n        this(holder, 0.0)","explanation":"The this() delegation call must be the very first statement in the constructor body. No field assignments or other code can appear before it. See ek9 -h E05050 for details."}],"companions":[]}
{"id":581,"category":"Constructor Delegation","question":"How do I call a parent constructor using super()?","url":"https://ek9.io/qa/QA0581.html","alternatePhrasings":["How does super() work in EK9 constructors?","How do I initialize the parent class in a constructor?","What is super() in a constructor?"],"answer":"Use super() as the first statement in a child class constructor to call the parent class constructor. This ensures the parent is properly initialized before the child adds its own state.\n\nBASIC SUPER() CALL\nCall super() with arguments matching a parent constructor:\n  Child()\n    -> name as String, age as Integer\n    super(name)  //calls Parent(String) constructor\n    this.age: age\n\nMUST BE FIRST STATEMENT\nsuper() must be the first statement in the constructor body (E05050). No code can execute before the parent is initialized.\n\nREQUIRES EXPLICIT PARENT\nsuper() is only valid when the class explicitly extends another class. Using super() without an explicit parent produces E05040.\n\nPARAMETER PASSING\nPass the appropriate arguments to match the parent constructor:\n  ChildClass()\n    -> a as String, b as Integer\n    super(a)  //passes 'a' to parent constructor\n\nSee Q580 for this() delegation. See Q582 for delegation order. See Q583 for this() vs super() restrictions. See Q584 for missing super() call.","ek9Example":"defines module qa.constructor.superdelegation\n\n  defines class\n\n    //Parent class: must be open for extension\n    Entity as open\n      entityName <- String()\n\n      Entity()\n        -> entityName as String\n        this.entityName: entityName\n\n      entityName() as pure\n        <- rtn as String: entityName\n\n      default operator ?\n\n    //Child calls super() to initialize parent\n    Person extends Entity as open\n      personAge <- 0\n\n      Person()\n        ->\n          entityName as String\n          personAge as Integer\n        super(entityName)\n        this.personAge: personAge\n\n      personAge() as pure\n        <- rtn as Integer: personAge\n\n      default operator ?\n\n    //Grandchild also uses super()\n    Employee extends Person\n      role <- String()\n\n      Employee()\n        ->\n          entityName as String\n          personAge as Integer\n          role as String\n        super(entityName, personAge)\n        this.role: role\n\n      role() as pure\n        <- rtn as String: role\n\n      default operator ?\n\n  defines program\n\n    SuperDelegationDemo()\n      stdout <- Stdout()\n\n      entity <- Entity(\"Base\")\n      stdout.println(`Entity: ${entity.entityName()}`)\n\n      person <- Person(\"Alice\", 30)\n      stdout.println(`Person: ${person.entityName()}, age ${person.personAge()}`)\n\n      emp <- Employee(\"Bob\", 25, \"Developer\")\n      stdout.println(`Employee: ${emp.entityName()}, age ${emp.personAge()}, role ${emp.role()}`)","migrationContext":"Java: super() must be first statement, implicit if parent has no-arg constructor. Python: super().__init__() can be called anywhere. Kotlin: primary constructor delegates via super in class header. C#: base() constructor chaining. EK9: super() must be first statement, E05050 if not, E05040 if no explicit parent.","keywords":["E05040","E05050","constant","constructor","delegation","first","initialize","parent","super","this"],"primaryTopics":["super constructor","super call"],"typicalErrors":[{"error":"E05050","correct":"Person()\n        ->\n          entityName as String\n          personAge as Integer\n        super(entityName)\n        this.personAge: personAge","incorrect":"Person()\n        ->\n          entityName as String\n          personAge as Integer\n        this.personAge: personAge\n        super(entityName)","explanation":"The super() call must be the very first statement in the constructor body. No field assignments can appear before the parent is initialized. See ek9 -h E05050 for details."},{"error":"E50060","correct":"stdout.println(`Entity: ${entity.entityName()}`)","incorrect":"stdout.println(entity.entityName().toUpperCase())","explanation":"String has no toUpperCase() method in EK9. Use upperCase() instead. See ek9 -h E50060 for details."}],"companions":[]}
{"id":582,"category":"Constructor Delegation","question":"Why must this() or super() be the first statement in a constructor?","url":"https://ek9.io/qa/QA0582.html","alternatePhrasings":["What is E05050 delegation not first statement?","Can I run code before this() or super()?","Why does my constructor delegation fail with E05050?"],"answer":"In EK9, this() or super() must be the very first statement in a constructor body. Any code before the delegation call produces error E05050.\n\nTHE RULE\nDelegation calls (this() or super()) must be the first executable statement. No variable assignments, method calls, or assertions can come before them.\n\nTHE ERROR: E05050\nE05050 fires when code appears before this() or super(). The compiler requires delegation to happen before any other logic.\n\nWHY FIRST STATEMENT\nObject initialization order must be predictable. The parent (or delegated constructor) sets up fundamental state. Child code runs after the base is established. This prevents accessing uninitialized parent state.\n\nCORRECT PATTERN\nPut all validation and logic AFTER the delegation:\n  Child()\n    -> value as Integer\n    super(value)       // First statement\n    assert value > 0   // Validation after\n\nPARAMETER VALIDATION\nIf you need to validate parameters before construction, use a factory function or validate in the delegated constructor itself.\n\nSee Q580 for this() delegation. See Q581 for super() delegation. See Q583 for this() vs super() restrictions.","ek9Example":"defines module qa.constructor.delegationorder\n\n  defines class\n\n    Base as open\n      label <- String()\n\n      Base()\n        -> label as String\n        this.label: label\n\n      label() as pure\n        <- rtn as String: label\n\n      default operator ?\n\n    //Correct: super() is the first statement\n    Derived extends Base\n      priority <- 0\n\n      Derived()\n        ->\n          label as String\n          priority as Integer\n        super(label)\n        this.priority: priority\n\n      //Another constructor: this() is the first statement\n      Derived()\n        -> label as String\n        this(label, 1)\n\n      priority() as pure\n        <- rtn as Integer: priority\n\n      default operator ?\n\n  defines program\n\n    DelegationOrderDemo()\n      stdout <- Stdout()\n\n      item1 <- Derived(\"alpha\", 5)\n      stdout.println(`${item1.label()}: priority ${item1.priority()}`)\n\n      item2 <- Derived(\"beta\")\n      stdout.println(`${item2.label()}: priority ${item2.priority()}`)","migrationContext":"Java: this()/super() must be first statement (relaxed in Java 22). Python: super().__init__() can be anywhere. Kotlin: primary constructor handles this implicitly. C#: base()/this() must be in constructor header. EK9: E05050 enforces first-statement rule strictly.","keywords":["E05050","constant","constructor","delegation","first","order","statement","super","this"],"primaryTopics":[],"typicalErrors":[{"error":"E05050","correct":"Derived()\n        ->\n          label as String\n          priority as Integer\n        super(label)\n        this.priority: priority","incorrect":"Derived()\n        ->\n          label as String\n          priority as Integer\n        this.priority: priority\n        super(label)","explanation":"The super() call must be the very first statement. Assigning fields before calling super() means the parent is not yet initialized, which is invalid. See ek9 -h E05050 for details."},{"error":"E05050","correct":"Derived()\n        -> label as String\n        this(label, 1)","incorrect":"Derived()\n        -> label as String\n        priority: 1\n        this(label, 1)","explanation":"The this() delegation must be the first statement. No variable assignments or other code can precede it. See ek9 -h E05050 for details."}],"companions":[]}
{"id":583,"category":"Constructor Delegation","question":"Can I use both this() and super() in the same constructor?","url":"https://ek9.io/qa/QA0583.html","alternatePhrasings":["What is E05060 this and super together?","Why can't I call both this() and super()?","Can a constructor delegate to both this() and super()?"],"answer":"No. A constructor can use either this() or super(), but not both. Since delegation must be the first statement, only one delegation call is possible per constructor.\n\nTHE RULE\nA constructor body can contain at most one delegation call: either this() or super(), never both. E05060 covers the use of this()/super() outside constructors; in practice, the first-statement rule (E05050) prevents having both.\n\nCORRECT PATTERN: SEPARATE CONSTRUCTORS\nUse separate constructors for different delegation paths:\n  Child()\n    -> name as String\n    this(name, 0)          // this() delegation\n  Child()\n    -> name as String, count as Integer\n    super(name)            // super() delegation\n    this.count: count\n\nThe one-arg constructor delegates to the two-arg constructor via this(), and the two-arg constructor delegates to the parent via super(). Each constructor has exactly one delegation target.\n\nSee Q580 for this() delegation. See Q581 for super() delegation. See Q582 for delegation order. See Q585 for field initialization vs delegation.","ek9Example":"defines module qa.constructor.notboth\n\n  defines class\n\n    Animal as open\n      species <- String()\n\n      Animal()\n        -> species as String\n        this.species: species\n\n      species() as pure\n        <- rtn as String: species\n\n      default operator ?\n\n    //Correct: this() and super() in SEPARATE constructors\n    Pet extends Animal\n      petName <- String()\n\n      //This constructor uses this() delegation\n      Pet()\n        -> petName as String\n        this(petName, \"Unknown\")\n\n      //This constructor uses super() delegation\n      Pet()\n        ->\n          petName as String\n          species as String\n        super(species)\n        this.petName: petName\n\n      petName() as pure\n        <- rtn as String: petName\n\n      default operator ?\n\n  defines program\n\n    NotBothDemo()\n      stdout <- Stdout()\n\n      //Uses this() path\n      pet1 <- Pet(\"Buddy\")\n      stdout.println(`${pet1.petName()} is a ${pet1.species()}`)\n\n      //Uses super() path directly\n      pet2 <- Pet(\"Max\", \"Dog\")\n      stdout.println(`${pet2.petName()} is a ${pet2.species()}`)","migrationContext":"Java: same rule, cannot have both this() and super(). Kotlin: primary constructor handles implicit super. C#: same rule, choose this() or base(). EK9: same rule, one delegation per constructor.","keywords":["E05060","both","constant","constructor","delegation","exclusive","mutual","super","this"],"primaryTopics":[],"typicalErrors":[{"error":"E05050","correct":"Pet()\n        -> petName as String\n        this(petName, \"Unknown\")","incorrect":"Pet()\n        -> petName as String\n        this(petName, \"Unknown\")\n        super(\"Cat\")","explanation":"A constructor can use either this() or super(), but not both. Since delegation must be the first statement, only one delegation call is possible per constructor. See ek9 -h E05050 for details."},{"error":"E05050","correct":"Pet()\n        ->\n          petName as String\n          species as String\n        super(species)\n        this.petName: petName","incorrect":"Pet()\n        ->\n          petName as String\n          species as String\n        this.petName: petName\n        super(species)","explanation":"The super() delegation must be the first statement in the constructor body. Field assignments cannot appear before it. See ek9 -h E05050 for details."}],"companions":[]}
{"id":584,"category":"Constructor Delegation","question":"When is an explicit super() call required?","url":"https://ek9.io/qa/QA0584.html","alternatePhrasings":["What is E05080 super without explicit parent?","When must I call super() in a constructor?","Does EK9 have implicit super() calls?"],"answer":"super() is only needed when the parent class requires constructor arguments. If the parent has a default (no-arg) constructor, EK9 implicitly calls it. E05040 fires if you call super() without an explicit parent class. E05080 fires if you use 'super' outside a class with an explicit parent.\n\nWHEN SUPER() IS NEEDED\nWhen the parent class only has constructors with parameters, the child must explicitly call super() with the right arguments. Without it, the parent cannot be initialized.\n\nWHEN SUPER() IS IMPLICIT\nIf the parent has a no-arg constructor (or a default constructor), and the child constructor does not explicitly call super(), the parent no-arg constructor is called implicitly.\n\nE05040: NO EXPLICIT PARENT\nIf you call super() in a class that does not explicitly extend another class, the compiler reports E05040.\n\nE05080: SUPER WITHOUT PARENT\nIf you use 'super' (the keyword, not just the call) in a class that does not extend another, the compiler reports E05080.\n\nSee Q580 for this() delegation. See Q581 for super() basics. See Q582 for delegation order. See Q587 for multi-level constructor hierarchies.","ek9Example":"defines module qa.constructor.missingsuper\n\n  defines class\n\n    //Parent with required parameter: child MUST call super()\n    NamedEntity as open\n      entityName <- String()\n\n      NamedEntity()\n        -> entityName as String\n        this.entityName: entityName\n\n      entityName() as pure\n        <- rtn as String: entityName\n\n      default operator ?\n\n    //Child must call super() because parent has no default constructor\n    TaggedEntity extends NamedEntity\n      tag <- String()\n\n      TaggedEntity()\n        ->\n          entityName as String\n          tag as String\n        super(entityName)\n        this.tag: tag\n\n      tag() as pure\n        <- rtn as String: tag\n\n      default operator ?\n\n    //Parent with default constructor: super() is implicit\n    SimpleBase as open\n      default SimpleBase()\n\n      baseLabel() as pure\n        <- rtn as String: \"simple\"\n\n      default operator ?\n\n    //Child does not need explicit super() call\n    SimpleDerived extends SimpleBase\n      derivedLabel <- String()\n\n      SimpleDerived()\n        -> derivedLabel as String\n        //No super() needed: SimpleBase() is called implicitly\n        this.derivedLabel: derivedLabel\n\n      derivedLabel() as pure\n        <- rtn as String: derivedLabel\n\n      default operator ?\n\n  defines program\n\n    MissingSuperCallDemo()\n      stdout <- Stdout()\n\n      //Explicit super() required\n      tagged <- TaggedEntity(\"Server\", \"production\")\n      stdout.println(`${tagged.entityName()}: ${tagged.tag()}`)\n\n      //Implicit super() (no-arg parent)\n      simple <- SimpleDerived(\"extra\")\n      stdout.println(`${simple.baseLabel()}: ${simple.derivedLabel()}`)","migrationContext":"Java: implicit super() if parent has no-arg constructor. Python: must explicitly call super().__init__(). Kotlin: delegation in class header. C#: implicit base() if no-arg exists. EK9: implicit no-arg super(), explicit required when parent needs arguments, E05040/E05080 for incorrect use.","keywords":["E05040","E05080","constructor","delegation","explicit","implicit","missing","parent","super","this"],"primaryTopics":[],"typicalErrors":[{"error":"E50060","correct":"stdout.println(`${tagged.entityName()}: ${tagged.tag()}`)","incorrect":"stdout.println(tagged.entityName().toUpperCase())","explanation":"String has no toUpperCase() method in EK9. Use upperCase() instead. See ek9 -h E50060 for details."},{"error":"E05050","correct":"TaggedEntity()\n        ->\n          entityName as String\n          tag as String\n        super(entityName)\n        this.tag: tag","incorrect":"TaggedEntity()\n        ->\n          entityName as String\n          tag as String\n        this.tag: tag\n        super(entityName)","explanation":"The super() call must be the first statement. The parent must be fully initialized before the child assigns its own fields. See ek9 -h E05050 for details."}],"companions":[]}
{"id":585,"category":"Constructor Delegation","question":"How do field initialization and constructor delegation interact?","url":"https://ek9.io/qa/QA0585.html","alternatePhrasings":["When should I use field defaults vs constructor assignment?","How do inline field values work with constructors?","What is the field initialization order in EK9?"],"answer":"Fields in EK9 must be initialized inline at declaration. Constructors then optionally reassign fields using ':' or ':=?'. Understanding the interaction between field defaults and constructor assignment is essential.\n\nFIELD DEFAULTS\nEvery field must have an inline default:\n  name <- String()    //initialized to unset String\n  count <- 0          //initialized to 0\nFields cannot be left uninitialized (E08180).\n\nCONSTRUCTOR ASSIGNMENT\nConstructors use ':' to reassign fields after defaults are applied:\n  MyClass()\n    -> name as String\n    this.name: name  //reassigns from default to parameter\n\nORDER OF INITIALIZATION\nField defaults are applied when the object is created. Then the constructor body runs. If delegation is used, the delegated constructor runs first.\n\nCOMBINING WITH DELEGATION\nWhen using this() or super(), field defaults apply first, then the delegated constructor runs, then remaining statements in the calling constructor.\n\nSee Q580 for this() delegation. See Q581 for super() delegation. See Q104 for uninitialized properties. See Q563 for pure constructor field assignment.","ek9Example":"defines module qa.constructor.fieldinit\n\n  defines class\n\n    //Fields with defaults, constructors reassign\n    Configuration\n      hostname <- \"localhost\"\n      portNumber <- 8080\n      maxRetries <- 3\n\n      //One-arg: only changes hostname\n      Configuration()\n        -> hostname as String\n        this.hostname: hostname\n\n      //Two-arg: changes hostname and port\n      Configuration()\n        ->\n          hostname as String\n          portNumber as Integer\n        this.hostname: hostname\n        this.portNumber: portNumber\n\n      //Three-arg: changes all fields\n      Configuration()\n        ->\n          hostname as String\n          portNumber as Integer\n          maxRetries as Integer\n        this.hostname: hostname\n        this.portNumber: portNumber\n        this.maxRetries: maxRetries\n\n      hostname() as pure\n        <- rtn as String: hostname\n\n      portNumber() as pure\n        <- rtn as Integer: portNumber\n\n      maxRetries() as pure\n        <- rtn as Integer: maxRetries\n\n      default operator ?\n\n  defines program\n\n    FieldInitDelegationDemo()\n      stdout <- Stdout()\n\n      //One-arg: port and retries keep defaults\n      cfg1 <- Configuration(\"api.example.com\")\n      stdout.println(`${cfg1.hostname()}: ${cfg1.portNumber()}, retries ${cfg1.maxRetries()}`)\n\n      //Two-arg: retries keeps default\n      cfg2 <- Configuration(\"db.example.com\", 5432)\n      stdout.println(`${cfg2.hostname()}: ${cfg2.portNumber()}, retries ${cfg2.maxRetries()}`)\n\n      //Three-arg: all custom\n      cfg3 <- Configuration(\"cache.example.com\", 6379, 5)\n      stdout.println(`${cfg3.hostname()}: ${cfg3.portNumber()}, retries ${cfg3.maxRetries()}`)","migrationContext":"Java: fields can be uninitialized (get default zero/null values). Python: fields set in __init__. Kotlin: properties initialized in init block or primary constructor. EK9: fields must be initialized inline, E08180 if not, constructors reassign with ':'.","keywords":["E08180","constant","constructor","default","delegation","field","initialization","inline","order","super","this"],"primaryTopics":[],"typicalErrors":[{"error":"E04050","correct":"hostname <- \"localhost\"\n      portNumber <- 8080\n      maxRetries <- 3","incorrect":"hostname <- portNumber\n      portNumber <- 8080","explanation":"Using a field before it has been declared is a forward reference error. Fields must be declared before being referenced. See ek9 -h E04050 for details."}],"companions":[]}
{"id":586,"category":"Constructor Delegation","question":"How does constructor delegation work with pure constructors?","url":"https://ek9.io/qa/QA0586.html","alternatePhrasings":["Can pure constructors use this() and super()?","How do I chain pure constructors with delegation?","What are the rules for pure constructor delegation?"],"answer":"Pure constructors can use both this() and super() delegation, following the same first-statement rule. The key requirement is that ALL constructors must be consistently pure (E05190).\n\nPURE THIS() DELEGATION\nPure constructors can delegate to another pure constructor with this():\n  Config() as pure\n    -> host as String\n    this(host, 8080)\n  Config() as pure\n    -> host as String, port as Integer\n    this.host :=? host\n    this.port :=? port\n\nPURE SUPER() DELEGATION\nPure child constructors can call pure parent constructors with super():\n  Child() as pure\n    -> name as String, extra as Integer\n    super(name)\n    this.extra :=? extra\n\nCONSISTENCY REQUIREMENT\nIf the parent has pure constructors, and you extend it, your constructors must also be pure. If the parent has non-pure constructors, your constructors must also be non-pure.\n\nGUARDED ASSIGNMENT\nPure constructors use ':=?' for field assignment after delegation. The delegated constructor may have already set some fields.\n\nSee Q563 for pure constructor basics. See Q564 for all-or-nothing purity. See Q565 for multiple pure constructors. See Q580 for this() delegation.","ek9Example":"defines module qa.constructor.puredelegation\n\n  defines class\n\n    //Parent with pure constructors\n    Coordinates as open\n      latitude as Float: Float()\n      longitude as Float: Float()\n\n      Coordinates() as pure\n        ->\n          latitude as Float\n          longitude as Float\n        this.latitude :=? latitude\n        this.longitude :=? longitude\n\n      latitude() as pure\n        <- rtn as Float: latitude\n\n      longitude() as pure\n        <- rtn as Float: longitude\n\n      default operator ?\n\n    //Child: pure constructors with both this() and super()\n    Location extends Coordinates\n      locationName <- String()\n\n      //this() delegation in pure constructor\n      Location() as pure\n        -> locationName as String\n        this(locationName, 0.0, 0.0)\n\n      //super() delegation in pure constructor\n      Location() as pure\n        ->\n          locationName as String\n          latitude as Float\n          longitude as Float\n        super(latitude, longitude)\n        this.locationName :=? locationName\n\n      locationName() as pure\n        <- rtn as String: locationName\n\n      default operator ?\n\n  defines program\n\n    PureConstructorDelegationDemo()\n      stdout <- Stdout()\n\n      //this() delegation path\n      loc1 <- Location(\"Unknown\")\n      stdout.println(`${loc1.locationName()}: (${loc1.latitude()}, ${loc1.longitude()})`)\n\n      //super() delegation path\n      loc2 <- Location(\"London\", 51.5074, -0.1278)\n      stdout.println(`${loc2.locationName()}: (${loc2.latitude()}, ${loc2.longitude()})`)","migrationContext":"Java: no pure constructor concept. Kotlin: no constructor purity. EK9: pure constructors support this() and super() delegation, all constructors must be consistently pure (E05190), :=? for field assignment.","keywords":["E05190","chain","constant","constructor","delegation","guarded","immutable","isset","null-safe","pure","safe","side-effect","super","this"],"primaryTopics":[],"typicalErrors":[{"error":"E05190","correct":"Location() as pure\n        -> locationName as String\n        this(locationName, 0.0, 0.0)","incorrect":"Location()\n        -> locationName as String\n        this(locationName, 0.0, 0.0)","explanation":"If any constructor is pure, ALL constructors must be pure. Mixing pure and non-pure constructors in the same class is invalid. See ek9 -h E05190 for details."},{"error":"E05050","correct":"Location() as pure\n        ->\n          locationName as String\n          latitude as Float\n          longitude as Float\n        super(latitude, longitude)\n        this.locationName :=? locationName","incorrect":"Location() as pure\n        ->\n          locationName as String\n          latitude as Float\n          longitude as Float\n        this.locationName :=? locationName\n        super(latitude, longitude)","explanation":"Even in pure constructors, super() must be the first statement. No field assignments can precede the parent delegation. See ek9 -h E05050 for details."}],"companions":[]}
{"id":587,"category":"Constructor Delegation","question":"How do constructors work in multi-level class hierarchies?","url":"https://ek9.io/qa/QA0587.html","alternatePhrasings":["How do I pass parameters through a three-level constructor chain?","How does super() work with grandparent constructors?","What is the constructor pattern for deep hierarchies?"],"answer":"In multi-level hierarchies, each level uses super() to initialize its immediate parent. Parameters flow from the most derived class through each parent in the chain.\n\nTHREE-LEVEL PATTERN\nGrandparent defines base fields and constructor. Parent extends grandparent, adds fields, calls super() to grandparent. Child extends parent, adds fields, calls super() to parent. Each level only passes what its parent needs.\n\nPARAMETER FLOW\nChild receives all parameters and passes a subset to parent:\n  Employee(name, age, role)\n    super(name, age)  //Person gets name and age\n  Person(name, age)\n    super(name)        //Entity gets name\n  Entity(name)\n    this.name: name    //stores name\n\nEACH LEVEL INITIALIZES ITS OWN STATE\nEach constructor calls super() with the parent's parameters, then initializes its own fields. This keeps each class responsible for its own state.\n\nDEFAULT CONSTRUCTORS IN THE CHAIN\nIf any level has a default constructor, the child does not need explicit super() for that level.\n\nSee Q580 for this() delegation. See Q581 for super() delegation. See Q582 for delegation order. See Q575 for deep override chains.","ek9Example":"defines module qa.constructor.hierarchy\n\n  defines class\n\n    //Level 1: base entity\n    Entity as open\n      entityName <- String()\n\n      Entity()\n        -> entityName as String\n        this.entityName: entityName\n\n      entityName() as pure\n        <- rtn as String: entityName\n\n      default operator ?\n\n    //Level 2: adds age\n    Person extends Entity as open\n      personAge <- 0\n\n      Person()\n        ->\n          entityName as String\n          personAge as Integer\n        super(entityName)\n        this.personAge: personAge\n\n      personAge() as pure\n        <- rtn as Integer: personAge\n\n      default operator ?\n\n    //Level 3: adds department\n    Employee extends Person\n      department <- String()\n\n      Employee()\n        ->\n          entityName as String\n          personAge as Integer\n          department as String\n        super(entityName, personAge)\n        this.department: department\n\n      department() as pure\n        <- rtn as String: department\n\n      default operator ?\n\n  defines program\n\n    HierarchyConstructorsDemo()\n      stdout <- Stdout()\n\n      emp <- Employee(\"Alice\", 30, \"Engineering\")\n      stdout.println(`Name: ${emp.entityName()}`)\n      stdout.println(`Age: ${emp.personAge()}`)\n      stdout.println(`Dept: ${emp.department()}`)\n\n      //Polymorphic use\n      entities <- List() of Entity\n      entities += Entity(\"Base\")\n      entities += Person(\"Bob\", 25)\n      entities += Employee(\"Charlie\", 35, \"Sales\")\n\n      for entity in entities\n        stdout.println(`Entity: ${entity.entityName()}`)","migrationContext":"Java: same pattern, super() in each level. Python: super().__init__() chains through MRO. Kotlin: primary constructor in class header handles delegation. C#: base() at each level. EK9: super() at each level, parameters flow through chain.","keywords":["chain","class","constant","constructor","delegation","flow","grandparent","hierarchy","multi-level","parameter","super","this"],"primaryTopics":[],"typicalErrors":[{"error":"E05050","correct":"Employee()\n        ->\n          entityName as String\n          personAge as Integer\n          department as String\n        super(entityName, personAge)\n        this.department: department","incorrect":"Employee()\n        ->\n          entityName as String\n          personAge as Integer\n          department as String\n        this.department: department\n        super(entityName, personAge)","explanation":"super() must be the first statement in the constructor. The parent (Person) must be initialized before the child (Employee) assigns its own fields. See ek9 -h E05050 for details."},{"error":"E50060","correct":"stdout.println(`Name: ${emp.entityName()}`)","incorrect":"stdout.println(emp.entityName().toUpperCase())","explanation":"String has no toUpperCase() method in EK9. Use upperCase() instead. See ek9 -h E50060 for details."}],"companions":[]}
{"id":588,"category":"Function Extension","question":"How do functions extend other functions in EK9?","url":"https://ek9.io/qa/QA0588.html","alternatePhrasings":["How does function inheritance work in EK9?","Can functions extend abstract functions?","What does 'is' mean for functions?"],"answer":"In EK9, functions are types that can participate in type hierarchies. A concrete function can extend an abstract function using 'is' or 'extends'. This creates function polymorphism without classes.\n\nFUNCTION EXTENSION SYNTAX\nUse 'is' to extend an abstract function:\n  Transformer as pure abstract\n    -> input as String\n    <- result as String?\n\n  UpperTransformer is Transformer as pure\n    -> input as String\n    <- result as String: input.upperCase()\n\nWHY THIS IS UNIQUE\nIn most languages, callable polymorphism requires classes, interfaces, or trait objects. EK9 functions are TYPES with identity: they can be stored, passed, and collected in lists. Function extension gives you polymorphism without any class boilerplate.\n\nSIGNATURE MATCHING\nThe extending function must match the abstract function's signature: same parameter types and compatible return type. Mismatches produce E05140.\n\nSee Q51 for abstract functions. See Q49 for function basics. See Q589 for abstract function implementations. See Q592 for signature mismatch errors.","ek9Example":"defines module qa.function.extensionbasics\n\n  defines function\n\n    //Abstract function: defines the contract\n    Transformer as pure abstract\n      -> input as String\n      <- result as String?\n\n    //Concrete function extends abstract\n    UpperTransformer is Transformer as pure\n      -> input as String\n      <- result as String: input.upperCase()\n\n    //Another implementation\n    PrefixTransformer is Transformer as pure\n      -> input as String\n      <- result as String: \"PREFIX:\" + input\n\n    //Yet another implementation\n    LengthTransformer is Transformer as pure\n      -> input as String\n      <- result as String: $length input\n\n  defines program\n\n    FunctionExtensionBasicsDemo()\n      stdout <- Stdout()\n\n      //Direct calls\n      stdout.println(UpperTransformer(\"hello\"))\n      stdout.println(PrefixTransformer(\"world\"))\n      stdout.println(LengthTransformer(\"test\"))\n\n      //Polymorphic use through abstract type\n      transformers <- List() of Transformer\n      transformers += UpperTransformer\n      transformers += PrefixTransformer\n      transformers += LengthTransformer\n\n      for transformer in transformers\n        stdout.println(transformer(\"ek9\"))","migrationContext":"Java: functional interfaces + lambda, no function type hierarchies. Python: no function typing. Rust: Fn traits are structural, not nominal. Go: function types exist but no inheritance. Kotlin: functional types are structural. Swift: closures are structural types, extensions add methods to existing types but no function type hierarchies. EK9: functions are nominal types with 'is'/'extends' hierarchies, unique function polymorphism.","keywords":["abstract","extend","extends","extension","function","handler","hierarchy","is","polymorphism","sealed","swift","type","visitor"],"primaryTopics":["function extension","extend function","function hierarchy"],"typicalErrors":[{"error":"E05150","correct":"UpperTransformer is Transformer as pure\n      -> input as String\n      <- result as String: input.upperCase()","incorrect":"UpperTransformer is Transformer\n      -> input as String\n      <- result as String: input.upperCase()","explanation":"When the abstract function is pure, all extending functions must also be pure. Omitting 'as pure' violates the purity contract and triggers E05150 because 'pure' in super requires 'pure' for the extending definition. See ek9 -h E05150 for details."}],"companions":[]}
{"id":589,"category":"Function Extension","question":"How do I implement an abstract function with multiple implementations?","url":"https://ek9.io/qa/QA0589.html","alternatePhrasings":["How do I create multiple function implementations?","Can I have several functions extending the same abstract?","How do I use abstract functions for strategy pattern?"],"answer":"An abstract function can have any number of concrete implementations. Each implementation uses 'is' to extend the abstract and must match the signature exactly.\n\nMULTIPLE IMPLEMENTATIONS\nDefine the abstract once, implement many times:\n  MathOp as pure abstract\n    -> a as Float, b as Float\n    <- result as Float?\n\n  AddOp is MathOp as pure\n    -> a as Float, b as Float\n    <- result as Float: a + b\n\n  SubOp is MathOp as pure\n    -> a as Float, b as Float\n    <- result as Float: a - b\n\nSTRATEGY PATTERN\nStore implementations in variables typed as the abstract function:\n  currentOp as MathOp: AddOp\n  result <- currentOp(10.0, 5.0)\nSwap implementations at runtime without classes.\n\nLIST OF FUNCTIONS\nCollect implementations for iteration:\n  ops <- [AddOp, SubOp, MulOp]\n  for op in ops\n    stdout.println(op(10.0, 5.0))\n\nSee Q588 for function extension basics. See Q51 for abstract functions. See Q214 for strategy pattern. See Q590 for dynamic function implementations.","ek9Example":"defines module qa.function.abstractimpl\n\n  defines function\n\n    //Abstract function: math operation contract\n    MathOp as pure abstract\n      ->\n        operandA as Float\n        operandB as Float\n      <- result as Float?\n\n    AddOp is MathOp as pure\n      ->\n        operandA as Float\n        operandB as Float\n      <- result as Float: operandA + operandB\n\n    SubtractOp is MathOp as pure\n      ->\n        operandA as Float\n        operandB as Float\n      <- result as Float: operandA - operandB\n\n    MultiplyOp is MathOp as pure\n      ->\n        operandA as Float\n        operandB as Float\n      <- result as Float: operandA * operandB\n\n  defines program\n\n    AbstractFunctionImplDemo()\n      stdout <- Stdout()\n\n      //Direct calls\n      stdout.println(`Add: ${AddOp(10.0, 3.0)}`)\n      stdout.println(`Sub: ${SubtractOp(10.0, 3.0)}`)\n      stdout.println(`Mul: ${MultiplyOp(10.0, 3.0)}`)\n\n      //Strategy pattern: swap at runtime\n      currentOp as MathOp: AddOp\n      stdout.println(`Current (add): ${currentOp(5.0, 2.0)}`)\n\n      currentOp: MultiplyOp\n      stdout.println(`Current (mul): ${currentOp(5.0, 2.0)}`)\n\n      //List of operations\n      operations <- List() of MathOp\n      operations += AddOp\n      operations += SubtractOp\n      operations += MultiplyOp\n\n      for operation in operations\n        stdout.println(`Result: ${operation(8.0, 4.0)}`)","migrationContext":"Java: strategy pattern requires interface + multiple classes. Python: pass different functions directly (no type checking). Rust: closures with Box<dyn Fn>. Kotlin: functional types + lambda. EK9: abstract function + multiple 'is' implementations, true function polymorphism.","keywords":["abstract","extend","function","handler","implementation","list","multiple","polymorphism","sealed","strategy","swap","visitor"],"primaryTopics":[],"typicalErrors":[{"error":"E50001","correct":"AddOp is MathOp as pure\n      ->\n        operandA as Float\n        operandB as Float\n      <- result as Float: operandA + operandB","incorrect":"AddOp is MathOp as pure\n      ->\n        operandA as Float\n        operandB as Float\n      <- result as Float: operandA + operandB\n      stdout.println(result)","explanation":"A pure function cannot call non-pure methods like stdout.println(). Pure functions must be side-effect free. See ek9 -h E50001 for details."}],"companions":[]}
{"id":590,"category":"Function Extension","question":"Can dynamic functions extend abstract functions?","url":"https://ek9.io/qa/QA0590.html","alternatePhrasings":["How do I create inline function implementations?","Can I use dynamic functions with abstract function types?","How do I extend an abstract function inline?"],"answer":"Yes, dynamic functions can extend abstract functions. A dynamic function is created inline and implements the abstract function's contract, typically capturing values from the surrounding scope.\n\nDYNAMIC FUNCTION SYNTAX\nCreate a dynamic function that extends an abstract:\n  Formatter as pure abstract\n    -> text as String\n    <- rtn as String?\n\n  upper <- () is Formatter as pure (rtn:=? text.upperCase())\n\nCLOSURE CAPTURE\nDynamic functions capture values from their enclosing scope. The captured values become part of the function instance:\n  tag <- \"LOG\"\n  logger <- (tag) is Formatter as pure function\n    rtn:=? tag + \": \" + text\n\nINLINE VS MULTI-LINE\nInline form: () is Type as pure (body). Multi-line form: () is Type as pure function followed by indented body.\n\nWHEN TO USE DYNAMIC FUNCTIONS\nOne-off implementations that do not warrant a named function. Functions that need access to local variables (closures). Building function instances with different captured state.\n\nSee Q52 for dynamic function basics. See Q53 for closure capture. See Q588 for function extension basics. See Q591 for comparing function and class hierarchies.","ek9Example":"defines module qa.function.dynamicextends\n\n  defines function\n\n    //Abstract function contract\n    Formatter as pure abstract\n      -> text as String\n      <- rtn as String?\n\n    //Named implementation for comparison\n    PlainFormatter is Formatter as pure\n      -> text as String\n      <- rtn as String: text\n\n  defines program\n\n    DynamicFunctionExtendsDemo()\n      stdout <- Stdout()\n\n      //Named implementation\n      stdout.println(PlainFormatter(\"hello\"))\n\n      //Inline dynamic function (single-line body)\n      upper <- () is Formatter as pure (rtn:=? text.upperCase())\n      stdout.println(upper(\"hello\"))\n\n      //Dynamic function with captured variable (multi-line body)\n      tag <- \"LOG\"\n      logger <- (tag) is Formatter as pure function\n        rtn:=? `${tag}: ${text}`\n\n      stdout.println(logger(\"system started\"))\n\n      //Another capture with different value\n      errorTag <- \"ERROR\"\n      errorLogger <- (errorTag) is Formatter as pure function\n        rtn:=? `${errorTag}: ${text}`\n\n      stdout.println(errorLogger(\"disk full\"))\n\n      //All are Formatter type: polymorphic usage\n      formatters <- List() of Formatter\n      formatters += PlainFormatter\n      formatters += upper\n      formatters += logger\n      formatters += errorLogger\n\n      for item in formatters\n        stdout.println(item(\"test message\"))","migrationContext":"Java: lambdas implement functional interfaces. Python: lambdas are limited to expressions. Rust: closures implement Fn traits. Kotlin: lambdas implement functional types. EK9: dynamic functions extend abstract functions with closure capture.","keywords":["abstract","anonymous","capture","closure","dynamic","extend","extends","function","implement","inline"],"primaryTopics":[],"typicalErrors":[{"error":"E50001","correct":"PlainFormatter is Formatter as pure\n      -> text as String\n      <- rtn as String: text","incorrect":"PlainFormatter is Formatter as pure\n      -> text as String\n      <- rtn as String: text\n      stdout.println(text)","explanation":"A pure function cannot call non-pure methods. Pure functions must not produce side effects. See ek9 -h E50001 for details."}],"companions":[]}
{"id":591,"category":"Function Extension","question":"How do function hierarchies compare to class hierarchies?","url":"https://ek9.io/qa/QA0591.html","alternatePhrasings":["Should I use function extension or class inheritance?","What is the difference between function and class hierarchies?","When should I prefer functions over classes for polymorphism?"],"answer":"Both function hierarchies and class hierarchies provide polymorphism in EK9, but they serve different purposes. Functions are best for stateless transformations; classes are best for stateful entities.\n\nFUNCTION HIERARCHIES\nAbstract function defines a callable contract. Concrete functions implement it. No state (unless using closure capture). Best for: transformations, strategies, pipelines, predicates.\n\nCLASS HIERARCHIES\nAbstract class defines entity with state and behavior. Concrete classes extend it. Full state management with fields and methods. Best for: domain entities, stateful components, complex objects.\n\nWHEN TO USE FUNCTIONS\nSingle input-output transformations. Strategy pattern with no shared state. Stream pipeline stages (filter, map, etc.). Predicates and validators.\n\nWHEN TO USE CLASSES\nDomain objects with multiple fields. Objects with multiple methods. Stateful services and components. Complex object graphs.\n\nKEY DIFFERENCES\nFunctions cannot hold mutable state (closures capture values, not variables). Classes can have fields, methods, and operators. Functions are more concise for simple transformations. Classes provide richer abstractions.\n\nSee Q588 for function extension. See Q103 for abstract classes. See Q212 for composition over inheritance. See Q214 for strategy pattern.","ek9Example":"defines module qa.function.vsclass\n\n  defines constant\n\n    MIN_VALIDATION_LENGTH <- 2\n\n  defines function\n\n    //Function hierarchy: stateless transformation\n    Validator as pure abstract\n      -> input as String\n      <- isValid as Boolean?\n\n    LengthValidator is Validator as pure\n      -> input as String\n      <- isValid as Boolean: length input > MIN_VALIDATION_LENGTH\n\n    PatternValidator is Validator as pure\n      -> input as String\n      <- isValid as Boolean: input contains \"@\"\n\n  defines class\n\n    //Class hierarchy: stateful entity\n    Document as abstract\n      title <- String()\n\n      Document()\n        -> title as String\n        this.title: title\n\n      title() as pure\n        <- rtn as String: title\n\n      wordCount() as pure abstract\n        <- rtn as Integer?\n\n      default operator ?\n\n    TextDocument extends Document\n      content <- String()\n\n      TextDocument()\n        ->\n          title as String\n          content as String\n        super(title)\n        this.content: content\n\n      override wordCount() as pure\n        <- rtn as Integer: length content\n\n      default operator ?\n\n  defines program\n\n    FunctionVsClassHierarchyDemo()\n      stdout <- Stdout()\n\n      //Function hierarchy: simple validation\n      validators <- List() of Validator\n      validators += LengthValidator\n      validators += PatternValidator\n\n      testInput <- \"a@b\"\n      for validator in validators\n        stdout.println(`Valid: ${validator(testInput)}`)\n\n      //Class hierarchy: rich entity\n      doc <- TextDocument(\"Report\", \"This is the content of the report\")\n      stdout.println(`${doc.title()}: ${doc.wordCount()} chars`)","migrationContext":"Java: interfaces for behavior, classes for state. Python: first-class functions but no type hierarchy. Rust: Fn traits for functions, structs for state. Go: interfaces for behavior, structs for state. EK9: both functions and classes are nominal types with inheritance hierarchies.","keywords":["abstract","class","comparison","extend","function","handler","hierarchy","polymorphism","sealed","state","stateless","visitor","when"],"primaryTopics":[],"typicalErrors":[{"error":"E05050","correct":"TextDocument()\n        ->\n          title as String\n          content as String\n        super(title)\n        this.content: content","incorrect":"TextDocument()\n        ->\n          title as String\n          content as String\n        this.content: content\n        super(title)","explanation":"The super() call must be the very first statement in a child class constructor. No code can execute before the parent is initialized. See ek9 -h E05050 for details."},{"error":"E05120","correct":"override wordCount() as pure\n        <- rtn as Integer: length content","incorrect":"wordCount() as pure\n        <- rtn as Integer: length content","explanation":"When overriding an abstract method from a parent class, the 'override' keyword is required. Omitting it triggers a missing override error. See ek9 -h E05120 for details."}],"companions":[]}
{"id":592,"category":"Function Extension","question":"What happens if a function extension has a signature mismatch?","url":"https://ek9.io/qa/QA0592.html","alternatePhrasings":["What is E05140 function signature does not match super?","Why does my function extension fail with a signature error?","How do I fix a function signature mismatch?"],"answer":"When a function extends an abstract function, the signatures must match. If the parameter types, parameter count, or return type differ, the compiler reports E05140.\n\nTHE ERROR: E05140\nE05140 fires when a function declares 'is ParentFunction' but the signatures do not match. Common mismatches: different parameter types, wrong parameter count, incompatible return type.\n\nSIGNATURE MATCHING RULES\nParameter count must be identical. Parameter types must be identical (or contravariant). Return type must be identical (or covariant). Parameter names can differ (names are local).\n\nCOMMON MISTAKES\nWrong parameter type (Integer vs String). Extra or missing parameters. Return type mismatch.\n\nCORRECT APPROACH\nCheck the abstract function signature and match it exactly in the implementation. Parameter names can be different, but types must match.\n\nSee Q588 for function extension basics. See Q589 for multiple implementations. See Q594 for extension rules.","ek9Example":"defines module qa.function.signaturematch\n\n  defines function\n\n    //Abstract with specific signature\n    StringProcessor as pure abstract\n      -> input as String\n      <- output as String?\n\n    //Correct: exact signature match (parameter names differ, types match)\n    TrimProcessor is StringProcessor as pure\n      -> input as String\n      <- output as String: input.trim()\n\n    //Correct: another match\n    UpperProcessor is StringProcessor as pure\n      -> input as String\n      <- output as String: input.upperCase()\n\n    //Different abstract with two parameters\n    Combiner as pure abstract\n      ->\n        first as String\n        second as String\n      <- combined as String?\n\n    //Correct: matches two-parameter signature\n    SpaceCombiner is Combiner as pure\n      ->\n        first as String\n        second as String\n      <- combined as String: `${first} ${second}`\n\n    DashCombiner is Combiner as pure\n      ->\n        first as String\n        second as String\n      <- combined as String: `${first}-${second}`\n\n  defines program\n\n    FunctionSignatureMatchDemo()\n      stdout <- Stdout()\n\n      //Single-parameter functions\n      processors <- List() of StringProcessor\n      processors += TrimProcessor\n      processors += UpperProcessor\n\n      for processor in processors\n        stdout.println(processor(\"  hello  \"))\n\n      //Two-parameter functions\n      combiners <- List() of Combiner\n      combiners += SpaceCombiner\n      combiners += DashCombiner\n\n      for combiner in combiners\n        stdout.println(combiner(\"hello\", \"world\"))","migrationContext":"Java: functional interface method signature must match exactly. Kotlin: functional type signatures are structural. Rust: closure type must match Fn trait signature. EK9: E05140 enforces exact signature match for function extension.","keywords":["E05140","abstract","count","extend","function","match","mismatch","parameter","return","signature","type"],"primaryTopics":[],"typicalErrors":[{"error":"E50001","correct":"SpaceCombiner is Combiner as pure\n      ->\n        first as String\n        second as String\n      <- combined as String: `${first} ${second}`","incorrect":"SpaceCombiner is Combiner as pure\n      ->\n        first as String\n        second as String\n      <- combined as String: `${first} ${second}`\n      stdout.println(combined)","explanation":"A pure function cannot call non-pure methods. Pure functions must not produce any side effects. See ek9 -h E50001 for details."}],"companions":[]}
{"id":593,"category":"Function Extension","question":"Can functions form multi-level inheritance chains?","url":"https://ek9.io/qa/QA0593.html","alternatePhrasings":["Can a concrete function be extended further?","How deep can function hierarchies go?","Can I have Abstract -> Middle -> Concrete function chains?"],"answer":"EK9 functions can form multi-level chains: an abstract function at the top, with concrete implementations at each level. However, only abstract functions can be extended. A concrete function cannot be further extended.\n\nABSTRACT FUNCTION EXTENSION\nAbstract functions define contracts that concrete functions implement:\n  BaseOp as pure abstract\n    -> x as Integer\n    <- rtn as Integer?\n  DoubleOp is BaseOp as pure\n    -> x as Integer\n    <- rtn as Integer: x + x\n\nCONCRETE FUNCTIONS ARE CLOSED\nOnce a function provides an implementation (has a body), it cannot be extended further. Only abstract functions can be the base of a hierarchy.\n\nMULTIPLE IMPLEMENTATIONS\nAn abstract function can have many direct implementations, creating a flat hierarchy with one abstract parent and multiple concrete children.\n\nSee Q588 for function extension basics. See Q589 for multiple implementations. See Q594 for extension rules. See Q595 for pure abstract function constraints.","ek9Example":"defines module qa.function.multilevelchain\n\n  defines constant\n\n    MIN_FILTER_LENGTH <- 3\n\n  defines function\n\n    //Abstract function: base of hierarchy\n    Filter as pure abstract\n      -> input as String\n      <- passes as Boolean?\n\n    //Multiple direct implementations (flat hierarchy)\n    NonEmptyFilter is Filter as pure\n      -> input as String\n      <- passes as Boolean: length input > 0\n\n    LongEnoughFilter is Filter as pure\n      -> input as String\n      <- passes as Boolean: length input > MIN_FILTER_LENGTH\n\n    ContainsAtFilter is Filter as pure\n      -> input as String\n      <- passes as Boolean: input contains \"@\"\n\n  defines program\n\n    MultilevelFunctionChainDemo()\n      stdout <- Stdout()\n\n      //All implementations share the abstract type\n      filters <- List() of Filter\n      filters += NonEmptyFilter\n      filters += LongEnoughFilter\n      filters += ContainsAtFilter\n\n      testValues <- [\"\", \"hi\", \"hello\", \"user@host\"]\n\n      for testVal in testValues\n        stdout.println(`Testing: \"${testVal}\"`)\n        for filterFn in filters\n          stdout.println(`  Passes: ${filterFn(testVal)}`)","migrationContext":"Java: functional interfaces are flat (one method). Kotlin: function types are structural. Rust: Fn traits have no hierarchy. EK9: abstract functions can have multiple concrete implementations, concrete functions are closed.","keywords":["abstract","chain","closed","concrete","extend","extension","function","hierarchy","multi-level"],"primaryTopics":[],"typicalErrors":[{"error":"E50001","correct":"NonEmptyFilter is Filter as pure\n      -> input as String\n      <- passes as Boolean: length input > 0","incorrect":"NonEmptyFilter is Filter as pure\n      -> input as String\n      <- passes as Boolean: length input > 0\n      stdout.println(input)","explanation":"A pure function cannot call non-pure methods like stdout.println(). Pure functions must have no side effects. See ek9 -h E50001 for details."}],"companions":[]}
{"id":594,"category":"Function Extension","question":"What are the rules for when a function can or cannot be extended?","url":"https://ek9.io/qa/QA0594.html","alternatePhrasings":["When can I extend a function in EK9?","What functions can be used as a base type?","Why can't I extend a concrete function?"],"answer":"Only abstract functions can be extended. Concrete functions (those with an implementation body) are closed and cannot serve as a base for other functions.\n\nCAN EXTEND: ABSTRACT FUNCTIONS\nFunctions declared with 'as abstract' can be extended:\n  Predicate as pure abstract\n    -> item as String\n    <- result as Boolean?\n\nCANNOT EXTEND: CONCRETE FUNCTIONS\nFunctions with a body cannot be extended:\n  addOne() as pure\n    -> x as Integer\n    <- rtn as Integer: x + 1\n  //Cannot write: DoubleAddOne is addOne\n\nSIGNATURE MUST MATCH (E05140)\nThe extending function must match the abstract function's parameter types, count, and return type. Mismatches produce E05140.\n\nPURITY MUST BE COMPATIBLE\nIf the abstract function is pure, implementations must be pure (same E05150 rule as classes). If the abstract function is not pure, implementations can be either.\n\nSee Q588 for function extension basics. See Q592 for signature mismatch. See Q593 for multi-level chains. See Q595 for pure abstract function constraints.","ek9Example":"defines module qa.function.extensionrules\n\n  defines function\n\n    //Abstract: CAN be extended\n    Converter as pure abstract\n      -> input as Integer\n      <- output as String?\n\n    //Implementation 1\n    DecimalConverter is Converter as pure\n      -> input as Integer\n      <- output as String: $input\n\n    //Implementation 2\n    HexConverter is Converter as pure\n      -> input as Integer\n      <- output as String: \"0x\" + $input\n\n    //Concrete function: CANNOT be extended\n    doubleIt() as pure\n      -> number as Integer\n      <- result as Integer: number + number\n\n  defines program\n\n    FunctionExtensionRulesDemo()\n      stdout <- Stdout()\n\n      //Abstract function implementations\n      converters <- List() of Converter\n      converters += DecimalConverter\n      converters += HexConverter\n\n      for converter in converters\n        stdout.println(converter(255))\n\n      //Concrete function: used directly\n      stdout.println(`Double 5: ${doubleIt(5)}`)","migrationContext":"Java: functional interfaces can only have one abstract method. Kotlin: function types are structural, no extension. Rust: no function type inheritance. EK9: only abstract functions can be extended, concrete functions are closed, E05140 for signature mismatch.","keywords":["E05140","E05150","abstract","closed","concrete","extend","extension","function","immutable","purity","rules","side-effect"],"primaryTopics":[],"typicalErrors":[{"error":"E50001","correct":"doubleIt() as pure\n      -> number as Integer\n      <- result as Integer: number + number","incorrect":"doubleIt() as pure\n      -> number as Integer\n      <- result as Integer: number + number\n      stdout.println(result)","explanation":"A pure function cannot call non-pure methods. The 'as pure' contract forbids all side effects. See ek9 -h E50001 for details."}],"companions":[]}
{"id":595,"category":"Function Extension","question":"How does purity interact with abstract function extension?","url":"https://ek9.io/qa/QA0595.html","alternatePhrasings":["Must implementations of a pure abstract function be pure?","What is E05150 for function extension?","How does purity flow through function hierarchies?"],"answer":"If an abstract function is declared 'as pure', every implementation must also be 'as pure'. This is the same E05150 rule that applies to class method overrides. Purity is a contract from the abstract definition.\n\nPURE ABSTRACT FUNCTION\nWhen an abstract function is pure, the contract guarantees no side effects for all implementations:\n  Hasher as pure abstract\n    -> input as String\n    <- hash as Integer?\n\nIMPLEMENTATIONS MUST BE PURE\nEvery function using 'is Hasher' must also be pure:\n  SimpleHasher is Hasher as pure\n    -> input as String\n    <- hash as Integer: #? input\n\nCANNOT ADD PURITY\nIf the abstract function is NOT pure, implementations cannot add purity (E05160). Purity flows from the abstract definition.\n\nWHY PURITY MATTERS FOR FUNCTIONS\nCode using the abstract type expects consistent behavior. If some implementations are pure and others are not, callers cannot reason about side effects.\n\nSee Q560 for pure method basics. See Q561 for purity in overrides. See Q588 for function extension basics. See Q594 for extension rules.","ek9Example":"defines module qa.function.pureabstract\n\n  defines function\n\n    //Pure abstract: all implementations must be pure\n    Scorer as pure abstract\n      -> text as String\n      <- score as Integer?\n\n    LengthScorer is Scorer as pure\n      -> text as String\n      <- score as Integer: length text\n\n    UpperScorer is Scorer as pure\n      -> text as String\n      <- score as Integer: length text.upperCase()\n\n    //Non-pure abstract: implementations can be impure\n    Reporter as abstract\n      -> message as String\n      <- report as String?\n\n    SimpleReporter is Reporter\n      -> message as String\n      <- report as String: \"[REPORT] \" + message\n\n  defines program\n\n    PureAbstractFunctionDemo()\n      stdout <- Stdout()\n\n      //Pure function hierarchy\n      scorers <- List() of Scorer\n      scorers += LengthScorer\n      scorers += UpperScorer\n\n      for scorer in scorers\n        stdout.println(`Score: ${scorer(\"Hello World\")}`)\n\n      //Non-pure function hierarchy\n      reporter <- SimpleReporter\n      stdout.println(reporter(\"system ready\"))","migrationContext":"Java: no purity on functional interfaces. Kotlin: no purity on function types. Rust: Fn traits have no purity annotation. EK9: pure abstract functions enforce purity on all implementations via E05150.","keywords":["E05150","abstract","contract","extend","extension","function","immutable","implementation","pure","purity","side-effect"],"primaryTopics":[],"typicalErrors":[{"error":"E05150","correct":"LengthScorer is Scorer as pure\n      -> text as String\n      <- score as Integer: length text","incorrect":"LengthScorer is Scorer\n      -> text as String\n      <- score as Integer: length text","explanation":"When the abstract function is declared as pure, every implementation must also be pure. Omitting 'as pure' violates the purity contract. See ek9 -h E05150 for details."},{"error":"E50001","correct":"UpperScorer is Scorer as pure\n      -> text as String\n      <- score as Integer: length text.upperCase()","incorrect":"UpperScorer is Scorer as pure\n      -> text as String\n      <- score as Integer: length text.upperCase()\n      stdout.println(score)","explanation":"A pure function cannot call non-pure methods like stdout.println(). The purity contract inherited from the abstract function forbids side effects. See ek9 -h E50001 for details."}],"companions":[]}
{"id":596,"category":"Functions and Methods","question":"What is the difference between a function and a method in EK9?","url":"https://ek9.io/qa/QA0596.html","alternatePhrasings":["How do functions differ from methods in EK9?","When should I use a function instead of a method in EK9?","Are functions and methods interchangeable in EK9?"],"answer":"Functions and methods in EK9 are fundamentally different constructs. Functions are stateless first-class types defined at module level. Methods are bound to the state of a class, trait, or record.\n\nFUNCTIONS ARE TYPES\nFunctions in EK9 are declared in a 'defines function' block at module level. Every function is a nominal type with its own identity. Functions can be stored in variables, passed as arguments, returned from other functions, and collected in lists. Functions do NOT have access to any class state because they exist outside of any class.\n\nMETHODS ARE BOUND TO STATE\nMethods are declared inside a class, trait, or record. They have access to the properties of their enclosing type via 'this'. Methods can have visibility modifiers (public, protected, private). Methods can be overridden in subclasses. Methods cannot be stored in variables independently of their object.\n\nSYNTAX COMPARISON\nBoth use '->' for parameters and '<-' for returns. Both can be marked 'as pure'. The syntax is deliberately similar so developers focus on the conceptual difference (stateless vs stateful) rather than syntactic differences.\n\nFUNCTION ADVANTAGES\nFunctions are testable in isolation (no object setup needed). Functions compose naturally (pass one function to another). Functions enable polymorphism without classes (via abstract function types). Functions enforce statelessness, which aids reasoning and parallelism.\n\nMETHOD ADVANTAGES\nMethods encapsulate state. Methods support inheritance hierarchies. Methods support dispatching on the receiver type. Methods enable the standard OOP patterns (encapsulation, polymorphism via classes).\n\nWHEN TO CHOOSE\nUse functions for pure computations, transformations, and pipeline stages. Use methods when behaviour is intrinsically tied to object state. Use abstract functions when you need callable polymorphism without classes.\n\nSee Q49 for function basics. See Q93 for class and method basics. See Q105 for method dispatch in classes. See Q255 for cost-based method resolution. See Q597 for function parameter patterns. See Q600 for method overloading vs function dispatching.","ek9Example":"defines module qa.functionsAndMethods.functionVsMethod\n\n  defines function\n\n    //Function: stateless, at module level, first-class type\n    formatName() as pure\n      ->\n        first as String\n        last as String\n      <- result as String: `${last}, ${first}`\n\n    //Abstract function: defines a callable contract\n    Transformer as pure abstract\n      -> input as String\n      <- output as String?\n\n    //Named function implementing abstract type\n    UpperTransformer is Transformer as pure\n      -> input as String\n      <- output as String: input.upperCase()\n\n  defines class\n\n    //Class with methods: methods access 'this' state\n    Greeter\n      prefix <- String()\n\n      Greeter()\n        -> prefix as String\n        this.prefix: prefix\n\n      //Method: accesses class state (prefix)\n      greet()\n        -> name as String\n        <- message as String: `${prefix} ${name}`\n\n      default operator ?\n\n  defines program\n\n    FunctionVsMethodDemo()\n      stdout <- Stdout()\n\n      // === FUNCTION: stateless, no object needed ===\n      stdout.println(formatName(\"Steve\", \"Limb\"))\n\n      // === METHOD: requires object with state ===\n      greeter <- Greeter(\"Hello\")\n      stdout.println(greeter.greet(\"World\"))\n\n      // === FUNCTION AS TYPE: store in variable ===\n      transformer as Transformer: UpperTransformer\n      stdout.println(transformer(\"hello world\"))\n\n      // === FUNCTIONS IN LISTS: polymorphic iteration ===\n      lower <- () is Transformer as pure (output:=? input.lowerCase())\n      transformers <- [UpperTransformer, lower]\n      for item in transformers\n        stdout.println(item(\"Mixed Case\"))","migrationContext":"Java: all functions must live inside classes as static methods or instance methods, no standalone functions, functional interfaces (SAM) are a workaround. Python: def creates functions at module level or methods inside classes, no formal distinction in type system. JavaScript: functions are standalone, methods are properties of objects, 'this' binding is confusing. Rust: fn defines standalone functions, impl blocks add methods to structs, methods take &self. Go: functions are standalone, methods use receiver syntax, no inheritance. Kotlin: top-level functions exist alongside class methods, extension functions blur the line. Swift: free functions and methods, protocol methods. EK9: functions are first-class nominal types at module level, methods are bound to class/trait/record state, both use identical syntax, functions can form type hierarchies via abstract.","keywords":["bound","class","difference","first-class","function","method","module","parameter","state","stateful","stateless","type"],"primaryTopics":["function vs method","method vs function"],"typicalErrors":[{"error":"E07520","correct":"default operator ?","incorrect":"operator ?","explanation":"When a class inherits operator ? from a base type, the implementation must use the 'override' keyword (or 'default' for auto-generated). Declaring bare 'operator ?' triggers E07520 because operator semantics require a Boolean return. See ek9 -h E07520 for details."},{"error":"E50060","correct":"stdout.println(formatName(\"Steve\", \"Limb\"))","incorrect":"stdout.println(formatName(\"Steve\", \"Limb\").toUpperCase())","explanation":"String has no toUpperCase() method in EK9. Use upperCase() instead. See ek9 -h E50060 for details."}],"companions":[]}
{"id":597,"category":"Functions and Methods","question":"How do function parameters work in EK9?","url":"https://ek9.io/qa/QA0597.html","alternatePhrasings":["What are the parameter passing rules in EK9?","How do I declare function parameters in EK9?","What is the difference between inline and block parameters?"],"answer":"EK9 function parameters use '->' for inputs and '<-' for the named return value. Parameters can be declared inline (single parameter) or in block style (multiple parameters).\n\nSINGLE PARAMETER INLINE\nFor a single parameter, declare it directly after '->':\n  greet()\n    -> name as String\n    <- message as String: \"Hello, \" + name\n\nMULTIPLE PARAMETERS BLOCK STYLE\nMultiple parameters are indented under '->':\n  add() as pure\n    ->\n      a as Integer\n      b as Integer\n    <- result as Integer: a + b\n\nRETURN VALUE\nThe return is declared with '<-' and a named variable. The compiler ensures all code paths initialise this variable. There is NO return statement in EK9.\n\nSANITIZED PARAMETERS\nThe 'sanitized' keyword marks parameters that come from untrusted sources. The compiler copies the value through a sanitizing constructor:\n  processInput()\n    -> userInput as sanitized String\n    <- cleaned as String: userInput.trim()\nThis prevents injection attacks at the language level.\n\nALL PARAMETERS MUST BE USED\nThe compiler errors (E08091) if a parameter is unused. Unused parameters are dead code: they increase signature complexity without contributing to the result. Abstract methods, overrides, and dispatchers are exempt from this check.\n\nPARAMETER PASSING SEMANTICS\nEK9 passes parameters by value for primitives and by reference for objects. Parameters are read-only by default in pure functions.\n\nSee Q49 for function basics. See Q54 for pure functions and Consumer vs Acceptor. See Q215 for sanitized parameters in depth. See Q319 for unused parameter detection. See Q596 for function vs method distinction. See Q598 for why EK9 has no default parameters. See Q599 for why EK9 has no varargs.","ek9Example":"defines module qa.functionsAndMethods.parameters\n\n  defines function\n\n    //Single parameter inline\n    greet() as pure\n      -> name as String\n      <- message as String: `Hello, ${name}`\n\n    //Multiple parameters block style\n    add() as pure\n      ->\n        a as Integer\n        b as Integer\n      <- result as Integer: a + b\n\n    //Return with body computation\n    clampValue() as pure\n      ->\n        number as Integer\n        minVal as Integer\n        maxVal as Integer\n      <- result as Integer: number\n      if number < minVal\n        result: minVal\n      else if number > maxVal\n        result: maxVal\n\n    //Sanitized parameter for untrusted input\n    cleanInput() as pure\n      -> userInput as sanitized String\n      <- cleaned as String: userInput.trim()\n\n  defines program\n\n    ParameterDemo()\n      stdout <- Stdout()\n\n      // === SINGLE PARAMETER ===\n      stdout.println(greet(\"World\"))\n\n      // === MULTIPLE PARAMETERS ===\n      stdout.println(`add(3, 4): ${add(3, 4)}`)\n\n      // === RETURN WITH BODY ===\n      stdout.println(`clamp(15, 0, 10): ${clampValue(15, 0, 10)}`)\n      stdout.println(`clamp(-5, 0, 10): ${clampValue(-5, 0, 10)}`)\n      stdout.println(`clamp(7, 0, 10): ${clampValue(7, 0, 10)}`)\n\n      // === SANITIZED PARAMETER ===\n      stdout.println(cleanInput(\"  trimmed  \"))","migrationContext":"Java: parameters declared in parentheses, types before names, no named returns, return statement required, no sanitization keyword, unused parameters not flagged. Python: def func(a, b): with dynamic typing, *args and **kwargs for variable arguments, default values, no compile-time unused check. JavaScript: function(a, b) with no type safety, arguments object, rest parameters, no compile-time checks. Rust: fn func(a: i32, b: i32) -> i32, explicit return type, return keyword used, no sanitization. Go: func add(a, b int) int, explicit return type, return keyword. Kotlin: fun add(a: Int, b: Int): Int, default parameter values supported. EK9: '->' for parameters, '<-' for named return, no return statement, sanitized keyword for security, unused parameters are compile errors, block style for multiple parameters.","keywords":["argument","block","function","immutable","inline","input","method","migrate","output","parameter","pure","return","sanitized","side-effect","style","unused"],"primaryTopics":[],"typicalErrors":[{"error":"E01072","correct":"<- result as Integer: number\n      if number < minVal\n        result: minVal\n      else if number > maxVal\n        result: maxVal","incorrect":"if number < minVal\n        return minVal\n      else if number > maxVal\n        return maxVal\n      return number","explanation":"EK9 has no return statement. Declare the return variable with a default and use if/else to modify it. See ek9 -h E01072 for details."},{"error":"E50060","correct":"stdout.println(greet(\"World\"))","incorrect":"stdout.println(greet(\"World\").toUpperCase())","explanation":"String has no toUpperCase() method in EK9. Use upperCase() instead. See ek9 -h E50060 for details."}],"companions":[]}
{"id":598,"category":"Functions and Methods","question":"Why doesn't EK9 have default parameter values?","url":"https://ek9.io/qa/QA0598.html","alternatePhrasings":["Can I set default values for function parameters in EK9?","How do I handle optional parameters without defaults in EK9?","What replaces default parameter values in EK9?"],"answer":"EK9 deliberately excludes default parameter values. This is a designed exclusion based on the problems defaults create in production code.\n\nWHY DEFAULTS WERE REMOVED\nDefault parameter values cause three categories of bugs. First, they hide complexity at call sites: callers cannot tell which defaults are being used without reading the function signature. Second, they create fragile APIs: changing a default value silently changes behaviour for all callers. Third, they complicate overload resolution: when combined with method overloading, defaults create ambiguous call resolution that even compiler writers find difficult to specify correctly.\n\nTHE EVIDENCE\nC++ default parameters combined with virtual methods create inheritance bugs (base vs derived defaults). Python mutable default arguments (def f(x=[])') are a well-known trap. Kotlin and Scala defaults with overloading create ambiguous resolution requiring explicit disambiguation.\n\nEK9 ALTERNATIVES\nConstructor overloading provides the same convenience for classes:\n  Config()\n    -> maxRetries as Integer\n    this(maxRetries, 30)\n  Config()\n    -> maxRetries as Integer, timeoutSeconds as Integer\n    this.maxRetries: maxRetries\n    this.timeoutSeconds: timeoutSeconds\n\nFunction delegation chains one function to another with explicit values:\n  connectSimple()\n    -> host as String\n    <- rtn as String: connectFull(host, 8080)\n\nEvery call site is explicit. Every value is visible. No hidden assumptions.\n\nSee Q49 for function basics. See Q94 for constructor overloading. See Q118 for builder pattern as alternative for many optional values. See Q580 for constructor delegation patterns. See Q597 for parameter syntax.","ek9Example":"defines module qa.functionsAndMethods.noDefaults\n\n  defines class\n\n    //Constructor overloading replaces default parameters\n    Config\n      maxRetries <- 0\n      timeoutSeconds <- 0\n\n      Config()\n        -> maxRetries as Integer\n        this(maxRetries, 30)\n\n      Config()\n        ->\n          maxRetries as Integer\n          timeoutSeconds as Integer\n        this.maxRetries: maxRetries\n        this.timeoutSeconds: timeoutSeconds\n\n      describe() as pure\n        <- rtn as String: `retries=${maxRetries}, timeout=${timeoutSeconds}`\n\n      default operator ?\n\n  defines function\n\n    //Function delegation: wraps another function with explicit values\n    connectSimple() as pure\n      -> host as String\n      <- rtn as String: connectFull(host, 8080)\n\n    connectFull() as pure\n      ->\n        host as String\n        port as Integer\n      <- rtn as String: `${host}:${port}`\n\n  defines program\n\n    NoDefaultParamsDemo()\n      stdout <- Stdout()\n\n      // === CONSTRUCTOR OVERLOADING (replaces default params) ===\n      simple <- Config(3)\n      stdout.println(`Simple: ${simple.describe()}`)\n\n      full <- Config(5, 60)\n      stdout.println(`Full: ${full.describe()}`)\n\n      // === FUNCTION DELEGATION (replaces default params) ===\n      stdout.println(connectSimple(\"localhost\"))\n      stdout.println(connectFull(\"localhost\", 9090))","migrationContext":"Java: no default parameters, method overloading used instead, builder pattern for many options. Python: def func(x, timeout=30) supports defaults, mutable default bug is well-known, overuse leads to functions with 10+ parameters. JavaScript: function(x, timeout = 30) with ES6 defaults, no type safety. Rust: no default parameters, builder pattern or Option types used instead. Go: no default parameters, functional options pattern (variadic WithXxx functions). Kotlin: fun connect(host: String, port: Int = 8080) with named defaults, can conflict with overloading. C#: optional parameters with default values, can break binary compatibility. Swift: func connect(host: String, port: Int = 8080) with defaults, named parameters help readability. EK9: no default parameters by design, use constructor overloading or function delegation, every value explicit at call site.","keywords":["absent","complexity","constructor","default","delegation","explicit","function","guard","hidden","method","migrate","optional","overload","parameter","safe","value"],"primaryTopics":[],"typicalErrors":[{"error":"E05050","correct":"Config()\n        -> maxRetries as Integer\n        this(maxRetries, 30)","incorrect":"Config()\n        -> maxRetries as Integer\n        maxRetries: maxRetries + 1\n        this(maxRetries, 30)","explanation":"The this() delegation call must be the very first statement in the constructor body. No code can execute before delegation. See ek9 -h E08110 for details."}],"companions":[]}
{"id":599,"category":"Functions and Methods","question":"Why doesn't EK9 have variable arguments (varargs)?","url":"https://ek9.io/qa/QA0599.html","alternatePhrasings":["Can I pass a variable number of arguments to a function in EK9?","What replaces varargs in EK9?","How do I handle unknown numbers of parameters in EK9?"],"answer":"EK9 deliberately excludes variable arguments (varargs). This is a designed exclusion based on the problems varargs create.\n\nWHY VARARGS WERE REMOVED\nVariable arguments lose type safety at boundaries. In Java, 'printf(String, Object...)' accepts anything, and type errors become runtime exceptions. Varargs hide the actual parameter count from complexity metrics, allowing functions to accept unbounded input while appearing simple. They also make APIs ambiguous when combined with overloading.\n\nTHE EVIDENCE\nJava varargs with generics produce heap pollution warnings. C printf-style varargs are a major source of security vulnerabilities (format string attacks). Python *args encourages functions that accept everything and validate nothing.\n\nEK9 ALTERNATIVE: PASS A LIST\nInstead of varargs, pass a typed List explicitly:\n  sumAll()\n    -> numbers as List of Integer\n    <- total as Integer: 0\n    for number in numbers\n      total += number\n\nCall with a list literal:\n  result <- sumAll([1, 2, 3, 4, 5])\n\nThis approach is typed (the compiler knows the element type), bounded (the list has a known length at the call site), and self-documenting (the parameter name describes the collection).\n\nSTREAM ALTERNATIVE\nFor transforming variable-length data, use stream pipelines:\n  items <- [\"hello\", \"world\", \"foo\"]\n  cat items > stdout\nPipelines handle collections of any size naturally.\n\nSee Q49 for function basics. See Q45 for List type. See Q597 for parameter syntax. See Q598 for why default parameters are also excluded.","ek9Example":"defines module qa.functionsAndMethods.noVarargs\n\n  defines function\n\n    //Instead of varargs: accept a typed List\n    sumAll()\n      -> numbers as List of Integer\n      <- total as Integer: 0\n      for number in numbers\n        total += number\n\n    //Another typed list alternative\n    joinAll()\n      ->\n        items as List of String\n        separator as String\n      <- result as String: String()\n      first <- true\n      for item in items\n        if first\n          result: item\n          first: false\n        else\n          result: `${result}${separator}${item}`\n\n  defines program\n\n    NoVarargsDemo()\n      stdout <- Stdout()\n\n      // === PASS A LIST INSTEAD OF VARARGS ===\n      total <- sumAll([1, 2, 3, 4, 5])\n      stdout.println(`Sum: ${total}`)\n\n      total2 <- sumAll([10, 20])\n      stdout.println(`Sum2: ${total2}`)\n\n      // === JOIN WITH SEPARATOR ===\n      joined <- joinAll([\"hello\", \"world\", \"foo\"], \", \")\n      stdout.println(`Joined: ${joined}`)\n\n      // === LIST LITERAL IS CONCISE ===\n      stdout.println(`Empty sum: ${sumAll(List() of Integer)}`)\n\n      // === STREAM PIPELINE FOR VARIABLE DATA ===\n      items <- [\"alpha\", \"beta\", \"gamma\"]\n      cat items > stdout","migrationContext":"Java: varargs with Object... or specific type, heap pollution with generics, autoboxing complications. Python: *args and **kwargs accept anything, no compile-time type checking. JavaScript: rest parameters (...args) with no type safety, arguments object legacy. Rust: no varargs, macros (println!) handle variable arguments at compile time. Go: variadic functions with ...Type, type-safe but only for last parameter. C: va_list varargs are type-unsafe, source of format string vulnerabilities. Kotlin: vararg keyword, similar to Java. Swift: variadic parameters with Type..., type-safe. EK9: no varargs by design, pass List of T explicitly, typed and bounded, stream pipelines for variable-length processing.","keywords":["arguments","bounded","collection","function","list","method","migrate","parameter","printf","stream","typed","varargs","variable"],"primaryTopics":[],"typicalErrors":[{"error":"E50060","correct":"${total}","incorrect":"${total.toString()}","explanation":"Integer has no toString() method in EK9; use the '$' prefix operator or string interpolation instead. See ek9 -h E50060 for details."},{"error":"E50060","correct":"joined <- joinAll([\"hello\", \"world\", \"foo\"], \", \")","incorrect":"joined <- joinAll([\"hello\", \"world\", \"foo\"], \", \").toUpperCase()","explanation":"String has no toUpperCase() method in EK9. Use upperCase() instead. See ek9 -h E50060 for details."}],"companions":[]}
{"id":600,"category":"Functions and Methods","question":"How does method overloading work compared to function dispatching?","url":"https://ek9.io/qa/QA0600.html","alternatePhrasings":["What is the difference between method overloading and dispatcher in EK9?","When should I use dispatcher instead of overloaded methods?","How does EK9 resolve overloaded method calls?"],"answer":"EK9 has two distinct mechanisms for calling different code based on types: method overloading (compile-time resolution) and dispatching (runtime resolution).\n\nMETHOD OVERLOADING\nClasses can have multiple methods with the same name but different parameter types. The compiler resolves which overload to call at COMPILE time using cost-based matching: exact type match (cost 0), subtype (cost 1), promotion like Integer to Float (cost 2), and Any match (cost 3). The lowest cost wins. If two overloads tie, the compiler reports an ambiguity error.\n\nDISPATCHER KEYWORD\nThe 'dispatcher' keyword enables RUNTIME type-based dispatch. Mark a method as dispatcher and provide overloaded implementations:\n  format() as dispatcher\n    -> item as Any\n    <- rtn as String: $item\n  format()\n    -> item as Integer\n    <- rtn as String: \"Int: \" + $item\nWhen called, the runtime checks the actual type of the argument and dispatches to the most specific overload.\n\nKEY DIFFERENCE\nWith overloading, if you have a variable typed as 'Any' that holds an Integer, the compiler selects the 'Any' overload. With dispatcher, the runtime selects the 'Integer' overload because it examines the actual type.\n\nFUNCTION DISPATCHING\nStandalone functions cannot share names in the same module. For function-level dispatch, use the 'as dispatcher' modifier. Dispatchers are limited to one or two parameters, must have the same parameter count across overloads, and must share the same purity.\n\nWHEN TO CHOOSE\nUse method overloading when the caller knows the exact type at compile time. Use dispatcher when processing heterogeneous collections or when the runtime type matters. Dispatcher eliminates the need for the visitor pattern.\n\nSee Q60 for function dispatching basics. See Q105 for method dispatch in class hierarchies. See Q255 for cost-based method resolution details. See Q596 for function vs method distinction.","ek9Example":"defines module qa.functionsAndMethods.overloadingVsDispatch\n\n  defines class\n\n    //Class with overloaded methods (compile-time resolution)\n    Formatter\n      formatItem() as pure\n        -> item as String\n        <- rtn as String: \"String: \" + item\n\n      formatItem() as pure\n        -> item as Integer\n        <- rtn as String: \"Integer: \" + $item\n\n      formatItem() as pure\n        -> item as Float\n        <- rtn as String: \"Float: \" + $item\n\n    //Class using dispatcher (runtime resolution)\n    RuntimeFormatter\n      describe() as dispatcher\n        -> item as Any\n        <- rtn as String: \"Any type\"\n\n      describe()\n        -> item as Integer\n        <- rtn as String: \"Int: \" + $item\n\n      describe()\n        -> item as String\n        <- rtn as String: \"Str: \" + item\n\n  defines program\n\n    OverloadingVsDispatchDemo()\n      stdout <- Stdout()\n\n      formatter <- Formatter()\n\n      // === COMPILE-TIME OVERLOADING ===\n      // Compiler selects overload based on declared type\n      stdout.println(formatter.formatItem(\"hello\"))\n      stdout.println(formatter.formatItem(42))\n      stdout.println(formatter.formatItem(3.14))\n\n      // === RUNTIME DISPATCHING ===\n      // Dispatcher selects overload based on runtime type\n      runtimeFmt <- RuntimeFormatter()\n\n      //Direct calls: same as overloading\n      stdout.println(runtimeFmt.describe(42))\n      stdout.println(runtimeFmt.describe(\"hello\"))\n\n      //Heterogeneous list: runtime dispatch shines here\n      items <- [1, 2.5, \"text\"]\n      for item in items\n        stdout.println(runtimeFmt.describe(item))","migrationContext":"Java: method overloading resolved at compile time, visitor pattern for runtime dispatch, instanceof checks as workaround, no multiple dispatch. Python: no method overloading (last definition wins), functools.singledispatch for single parameter. JavaScript: no overloading, manual type checking with typeof. Rust: no method overloading, trait dispatch for polymorphism, match on enums. Go: no overloading, type switch for runtime dispatch. Kotlin: method overloading like Java, when with is for runtime checks, no dispatcher. C#: method overloading at compile time, dynamic keyword for runtime. Julia: multiple dispatch built into language, most similar to EK9 dispatcher. EK9: method overloading with cost-based compile-time resolution, 'dispatcher' keyword for runtime type-based dispatch, eliminates visitor pattern.","keywords":["compile-time","cost","dispatch","dispatcher","handler","matching","method","overloading","parameter","resolution","runtime","sealed","type","visitor"],"primaryTopics":[],"typicalErrors":[{"error":"E50001","correct":"formatItem() as pure\n        -> item as String\n        <- rtn as String: \"String: \" + item","incorrect":"formatItem() as pure\n        -> item as String\n        <- rtn as String: \"String: \" + item\n        stdout.println(item)","explanation":"A pure method cannot call non-pure methods like stdout.println(). Pure methods must have no side effects. See ek9 -h E50001 for details."}],"companions":[]}
{"id":601,"category":"Functions and Methods","question":"How do dynamic functions replace nested functions in EK9?","url":"https://ek9.io/qa/QA0601.html","alternatePhrasings":["Can I define a function inside another function in EK9?","What replaces nested functions or local functions in EK9?","How do I create helper functions scoped to another function?"],"answer":"EK9 does not support nested function definitions. You cannot define a named function inside another function. Dynamic functions with closures serve the same purpose: they are created inline, capture variables from the enclosing scope, and exist only within that scope.\n\nWHY NO NESTED FUNCTIONS\nNested function definitions complicate scope analysis and name resolution. They create hidden coupling between the inner function and the outer function's variables. EK9 keeps function definitions flat (at module level) to make dependencies explicit.\n\nDYNAMIC FUNCTIONS AS LOCAL HELPERS\nDefine an abstract function type at module level, then create dynamic implementations inline where needed:\n  Validator as pure abstract\n    -> text as String\n    <- valid as Boolean?\n\nInside a program or function body, create a dynamic function:\n  checker <- (minLen) is Validator as pure function\n    valid:=? length text >= minLen\n\nThe dynamic function captures 'minLen' from the enclosing scope and implements the Validator contract. It behaves exactly like a nested function: scoped locally, accessing outer variables, callable only within the enclosing scope.\n\nINLINE SINGLE-EXPRESSION FORM\nFor simple helpers, use inline syntax:\n  notEmpty <- () is Validator as pure (valid:=? length text > 0)\n\nMULTIPLE LOCAL HELPERS\nCreate several dynamic functions in the same scope, each capturing different values:\n  short <- (shortLen: 3) is Validator as pure (valid:=? length text >= shortLen)\n  long <- (longLen: 10) is Validator as pure (valid:=? length text >= longLen)\n\nPOLYMORPHIC USE\nBecause all dynamic functions implement the same abstract type, they can be stored in lists and iterated polymorphically.\n\nSee Q52 for dynamic function syntax. See Q53 for closure capture mechanics. See Q115 for dynamic classes. See Q590 for dynamic function extension. See Q596 for function vs method distinction.","ek9Example":"defines module qa.functionsAndMethods.dynamicAsNested\n\n  defines function\n\n    //Abstract function type: the contract for validators\n    Validator as pure abstract\n      -> text as String\n      <- valid as Boolean?\n\n    //Named implementation for comparison\n    NotBlankValidator is Validator as pure\n      -> text as String\n      <- valid as Boolean: length text > 0\n\n  defines program\n\n    DynamicAsNestedDemo()\n      stdout <- Stdout()\n\n      // === DYNAMIC FUNCTION AS LOCAL HELPER ===\n\n      //Captures minLen from enclosing scope\n      minLen <- 5\n      lengthChecker <- (minLen) is Validator as pure function\n        valid:=? length text >= minLen\n\n      stdout.println(`\"hello\" valid (min 5): ${lengthChecker(\"hello\")}`)\n      stdout.println(`\"hi\" valid (min 5): ${lengthChecker(\"hi\")}`)\n\n      // === INLINE SINGLE-EXPRESSION FORM ===\n\n      notEmpty <- () is Validator as pure (valid:=? length text > 0)\n      stdout.println(`\"\" not empty: ${notEmpty(\"\")}`)\n      stdout.println(`\"x\" not empty: ${notEmpty(\"x\")}`)\n\n      // === MULTIPLE LOCAL HELPERS WITH DIFFERENT CAPTURES ===\n\n      shortLen <- 3\n      longLen <- 10\n      shortChecker <- (shortLen) is Validator as pure (valid:=? length text >= shortLen)\n      longChecker <- (longLen) is Validator as pure (valid:=? length text >= longLen)\n\n      testWord <- \"medium\"\n      stdout.println(`\"${testWord}\" short enough: ${shortChecker(testWord)}`)\n      stdout.println(`\"${testWord}\" long enough: ${longChecker(testWord)}`)\n\n      // === POLYMORPHIC: all are Validator type ===\n\n      validators <- [NotBlankValidator, notEmpty, lengthChecker, shortChecker, longChecker]\n      for validator in validators\n        stdout.println(`validates \"test\": ${validator(\"test\")}`)","migrationContext":"Java: no nested methods (until Java 22 unnamed classes), inner classes as workaround, lambdas for inline behaviour. Python: def inside def creates nested functions, closures capture by reference (late-binding bug). JavaScript: function inside function creates closures, common pattern but 'this' binding is error-prone. Rust: closures defined inline with |args| body, cannot define named fn inside fn (unstable), closures capture by reference or move. Go: anonymous functions inline, func() {} closures, capture by reference. Kotlin: local functions (fun inside fun) supported, closures capture mutable state. Swift: nested functions supported, closures for inline behaviour. EK9: no nested function definitions, dynamic functions with explicit capture serve the same purpose, scoped to enclosing function, polymorphic through abstract function types.","keywords":["abstract","anonymous","capture","closure","dynamic","function","helper","inline","local","method","nested","parameter","scope"],"primaryTopics":[],"typicalErrors":[{"error":"E05150","correct":"NotBlankValidator is Validator as pure\n      -> text as String\n      <- valid as Boolean: length text > 0","incorrect":"NotBlankValidator is Validator\n      -> text as String\n      <- valid as Boolean: length text > 0","explanation":"When the abstract function is declared as pure, all implementations must also be pure. Omitting 'as pure' triggers E05150 because 'pure' in super requires 'pure' for this definition. See ek9 -h E05150 for details."},{"error":"E50060","correct":"stdout.println(`\"hello\" valid (min 5): ${lengthChecker(\"hello\")}`)","incorrect":"stdout.println(lengthChecker(\"hello\").toString())","explanation":"Boolean has no toString() method in EK9. Use the $ prefix operator or string interpolation. See ek9 -h E50060 for details."}],"companions":[]}
{"id":602,"category":"Type Hierarchy Constraints","question":"What is a circular type hierarchy and why does EK9 ban it?","url":"https://ek9.io/qa/QA0602.html","alternatePhrasings":["Why can't a class extend itself in EK9?","What happens with circular inheritance in EK9?","How does EK9 prevent circular class hierarchies?"],"answer":"A circular type hierarchy occurs when a class directly or indirectly extends itself, creating an impossible loop. EK9 detects this at compile time and raises E05020.\n\nDIRECT CIRCULAR REFERENCE\nThe simplest case is a class extending itself:\n  A extends A  //ERROR: E05020 - A depends on itself\nThis is logically impossible because A cannot inherit from something that hasn't been defined yet.\n\nINDIRECT CIRCULAR CHAIN\nMore subtle cycles involve multiple classes:\n  A extends B\n  B extends A  //ERROR: E05020 - cycle A -> B -> A\nThe compiler traverses the full hierarchy chain and detects cycles at any depth.\n\nWHY BANNED\nCircular inheritance is logically impossible: if A inherits from B and B inherits from A, neither type can be fully defined. The method resolution order becomes undefined, field initialization creates infinite loops, and the type system becomes unsound.\n\nWHAT TO DO INSTEAD\nUse composition over inheritance. If two types need each other's features, extract the shared functionality into a common trait or base class.\n\nSee Q603 for multi-level chain detection. See Q604 for circular hierarchy checks on traits, records, and components. See Q610 for refactoring circular dependencies using composition. See Q101 for closed-by-default types.\nSee Q677 for abstract implementation chain. See Q679 for sealed allow-only. See Q697 for inheritance depth limit.\nSee Q729 for inheritance depth boundary example.","ek9Example":"defines module qa.typehierarchy.circular\n\n  defines class\n\n    //CORRECT: Linear hierarchy with no cycles\n    Vehicle as open\n      kind()\n        <- rtn as String: \"vehicle\"\n      default operator ?\n\n    Car extends Vehicle as open\n      override kind()\n        <- rtn as String: \"car\"\n      default operator ?\n\n    ElectricCar extends Car\n      override kind()\n        <- rtn as String: \"electric car\"\n      default operator ?\n\n  defines function\n\n    testLinearHierarchy()\n      vehicle <- Vehicle()\n      car <- Car()\n      electric <- ElectricCar()\n\n      require vehicle?\n      require car?\n      require electric?\n\n      //Each type in a clean linear chain: ElectricCar -> Car -> Vehicle\n      require vehicle.kind() == \"vehicle\"\n      require car.kind() == \"car\"\n      require electric.kind() == \"electric car\"","migrationContext":"Java: circular inheritance detected at compile time (same error). Python: MRO computation fails for circular hierarchies. C++: compilation error for circular inheritance. Rust: no class inheritance, traits cannot self-reference. Kotlin: compile error for circular inheritance. EK9: E05020 detects cycles at any depth in the hierarchy chain.","keywords":["E05020","chain","circular","composition","cycle","extends","hierarchy","inheritance","self-reference","type"],"primaryTopics":[],"typicalErrors":[{"error":"E05030","correct":"Car extends Vehicle as open","incorrect":"Car extends ElectricCar as open","explanation":"If Car extended ElectricCar which extends Car, a circular inheritance cycle would be created. The compiler traverses the full hierarchy chain and detects cycles at any depth. See ek9 -h E05030 for details."},{"error":"E05120","correct":"override kind()","incorrect":"kind()","explanation":"When a child class has a method matching a parent method, the override keyword is required. Omitting it causes method shadowing detection. See ek9 -h E05120 for details."}],"companions":[]}
{"id":603,"category":"Type Hierarchy Constraints","question":"How does EK9 detect multi-level circular inheritance chains?","url":"https://ek9.io/qa/QA0603.html","alternatePhrasings":["Can EK9 detect indirect circular inheritance?","What about three-way circular class hierarchies?","Does EK9 check deep circular inheritance chains?"],"answer":"EK9 traverses the entire inheritance chain during type hierarchy checks and detects circular references at any depth, not just direct self-references.\n\nTWO-LEVEL CYCLE\nA extends B, B extends A creates a cycle of length 2. The compiler walks A's parent chain and finds it leads back to A.\n\nTHREE-LEVEL CYCLE\nA extends B, B extends C, C extends A creates a cycle of length 3. Even though no class directly extends itself, the transitive chain A -> B -> C -> A is detected.\n\nDEEP CHAINS\nThe compiler checks arbitrarily deep chains. A cycle buried five levels deep is caught just as reliably as a direct self-reference. The traversal is O(n) in the depth of the hierarchy.\n\nHOW DETECTION WORKS\nDuring Phase 5 (TYPE_HIERARCHY_CHECKS), the compiler walks each type's supertype chain. If it encounters the starting type again, it reports E05020. This is a standard cycle detection algorithm applied to the inheritance graph.\n\nWHAT TO DO\nDraw out the inheritance relationships to visualize the cycle. Identify which class should be the root. Break the cycle by removing one extends clause. Consider composition instead.\n\nSee Q602 for circular hierarchy basics. See Q610 for composition-based refactoring. See Q315 for inheritance depth quality checks.","ek9Example":"defines module qa.typehierarchy.chaindetection\n\n  defines class\n\n    //CORRECT: Five-level linear hierarchy (no cycles)\n    Animal as open\n      describe()\n        <- rtn as String: \"animal\"\n      default operator ?\n\n    Mammal extends Animal as open\n      override describe()\n        <- rtn as String: \"mammal\"\n      default operator ?\n\n    Carnivore extends Mammal as open\n      override describe()\n        <- rtn as String: \"carnivore\"\n      default operator ?\n\n    Canine extends Carnivore as open\n      override describe()\n        <- rtn as String: \"canine\"\n      default operator ?\n\n    Wolf extends Canine\n      override describe()\n        <- rtn as String: \"wolf\"\n      default operator ?\n\n  defines function\n\n    testDeepLinearChain()\n      //Five levels deep: Wolf -> Canine -> Carnivore -> Mammal -> Animal\n      wolf <- Wolf()\n      require wolf?\n      require wolf.describe() == \"wolf\"\n\n      //Polymorphic use through the hierarchy\n      animals <- List() of Animal\n      animals += Animal()\n      animals += Mammal()\n      animals += Carnivore()\n      animals += Canine()\n      animals += Wolf()\n\n      for creature in animals\n        require creature?","migrationContext":"Java: detects circular inheritance at all depths (identical behavior). C#: detects cycles in class hierarchy. C++: compilation error for circular inheritance at any depth. Kotlin: compile error, same as Java. Python: raises TypeError when MRO cannot be computed. EK9: E05020 fires at any cycle depth, detected during TYPE_HIERARCHY_CHECKS phase.","keywords":["E05020","chain","circular","cycle","deep","detection","hierarchy","inherit","multi-level","transitive","type"],"primaryTopics":[],"typicalErrors":[{"error":"E50060","correct":"require wolf.describe() == \"wolf\"","incorrect":"require wolf.describe().toUpperCase() == \"wolf\"","explanation":"String has no toUpperCase() method in EK9. Use upperCase() instead. See ek9 -h E50060 for details."},{"error":"E05120","correct":"override describe()","incorrect":"describe()","explanation":"At every level of the five-level hierarchy, the override keyword is required when replacing the parent describe method. Omitting it triggers shadowing detection. See ek9 -h E05120 for details."}],"companions":[]}
{"id":604,"category":"Type Hierarchy Constraints","question":"Do traits, records, and components also check for circular hierarchies?","url":"https://ek9.io/qa/QA0604.html","alternatePhrasings":["Does EK9 check circular inheritance on traits?","Can records have circular hierarchies in EK9?","Is E05020 only for classes or all construct types?"],"answer":"E05020 applies to ALL EK9 construct types that support inheritance or implementation relationships, not just classes.\n\nTRAITS\nTraits can extend other traits, forming hierarchies. Circular trait chains (TraitA extends TraitB extends TraitA) are detected and rejected with E05020.\n\nRECORDS\nRecords that extend other records are subject to the same cycle detection. A record extending another record that extends the first is a compile error.\n\nCOMPONENTS\nComponents participate in hierarchies through trait implementation. Circular dependencies between components are detected.\n\nCLASSES WITH TRAITS\nEven when cycles involve a mix of classes and traits (a class implementing a trait that requires extending a class that circularly depends on the original), the compiler detects the cycle.\n\nUNIFORM ENFORCEMENT\nThe type hierarchy checker runs the same cycle detection algorithm across all construct types. This means you get the same error (E05020) regardless of whether the cycle involves classes, traits, records, or components.\n\nSee Q602 for circular hierarchy basics. See Q106 for trait hierarchies. See Q97 for records vs classes.","ek9Example":"defines module qa.typehierarchy.allconstructs\n\n  defines trait\n\n    //CORRECT: Linear trait hierarchy\n    Describable\n      describe() as abstract\n        <- rtn as String?\n\n    Displayable with trait of Describable\n      display() as abstract\n        <- rtn as String?\n\n  defines record\n\n    //CORRECT: Simple record (records are closed by default)\n    Point\n      xPos as Float: 0.0\n      yPos as Float: 0.0\n\n      Point()\n        ->\n          xPos as Float\n          yPos as Float\n        this.xPos: xPos\n        this.yPos: yPos\n\n      operator $ as pure\n        <- rtn as String: `(${xPos}, ${yPos})`\n\n      default operator ?\n\n  defines class\n\n    //Class implementing trait hierarchy correctly\n    Widget with trait of Displayable\n      label <- \"widget\"\n\n      Widget()\n        -> label as String\n        this.label: label\n\n      override describe()\n        <- rtn as String: label\n\n      override display()\n        <- rtn as String: `[${label}]`\n\n      default operator ?\n\n  defines function\n\n    testCorrectHierarchies()\n      point <- Point(3.0, 4.0)\n      require point?\n      require $point == \"(3.0, 4.0)\"\n\n      widget <- Widget(\"button\")\n      require widget?\n      require widget.describe() == \"button\"\n      require widget.display() == \"[button]\"","migrationContext":"Java: circular check on classes and interfaces. C#: circular check on classes, interfaces, and structs. Kotlin: circular check on all types including sealed. Rust: no class inheritance; trait coherence rules prevent cycles. Go: interfaces are structural, no circular hierarchy concept. EK9: uniform E05020 check across classes, traits, records, and components.","keywords":["E05020","circular","component","construct","cycle","hierarchy","inherit","record","trait","type","uniform"],"primaryTopics":[],"typicalErrors":[{"error":"E50060","correct":"require widget.describe() == \"button\"","incorrect":"require widget.describe().toUpperCase() == \"button\"","explanation":"String has no toUpperCase() method in EK9. Use upperCase() instead. See ek9 -h E50060 for details."},{"error":"E05120","correct":"override describe()","incorrect":"describe()","explanation":"When implementing abstract trait methods like describe from Describable, the override keyword is required to show explicit intent. See ek9 -h E05120 for details."}],"companions":[]}
{"id":605,"category":"Type Hierarchy Constraints","question":"Why can't I use 'this' inside a standalone function?","url":"https://ek9.io/qa/QA0605.html","alternatePhrasings":["What is E05070 in EK9?","Where is 'this' valid in EK9?","Why does using 'this' in a function cause an error?"],"answer":"The 'this' keyword refers to the current object instance, which only exists inside class methods, constructors, and operators. Standalone functions have no instance, so 'this' is meaningless. Using it triggers E05070.\n\nWHERE THIS IS VALID\n'this' is valid in instance methods of classes and records, constructors (accessing and initializing fields), operators (accessing the current object), and dynamic class bodies.\n\nWHERE THIS IS INVALID\n'this' is invalid in standalone functions (not bound to any instance), module-level code, and class-level constant expressions. These contexts have no object instance, so 'this' has nothing to refer to.\n\nCOMMON MISTAKE\nDevelopers coming from Java or C# sometimes write standalone functions and use 'this' as if they were static methods with access to instance state. In EK9, functions are truly standalone: they only have access to their parameters and closure captures.\n\nWHAT TO DO INSTEAD\nIf you need instance state, make it a method on a class. If the function just needs data, pass it as a parameter. If you accidentally wrote 'defines function' instead of a method inside a class, restructure the code.\n\nSee Q49 for standalone functions. See Q596 for function vs method distinction. See Q580 for using 'this' correctly in constructor delegation.","ek9Example":"defines module qa.typehierarchy.thiscontext\n\n  defines class\n\n    Counter\n      count <- 0\n\n      Counter()\n        -> initialCount as Integer\n        //this is valid in constructors\n        this.count: initialCount\n\n      increment()\n        //this is valid in methods\n        this.count: this.count + 1\n\n      currentCount() as pure\n        <- rtn as Integer: count\n\n      operator $ as pure\n        <- rtn as String: `Counter(${count})`\n\n      default operator ?\n\n  defines function\n\n    //Standalone function: no 'this' - only parameters and locals\n    doubleValue() as pure\n      -> inputValue as Integer\n      <- result as Integer: inputValue * 2\n\n    testThisContext()\n      //Functions work with parameters, not 'this'\n      doubled <- doubleValue(21)\n      require doubled == 42\n\n      //Classes use 'this' internally\n      counter <- Counter(10)\n      counter.increment()\n      require counter.currentCount() == 11","migrationContext":"Java: 'this' only in instance methods (static methods cannot use it). Python: 'self' must be explicit parameter, not available in module functions. C#: 'this' only in instance methods. Kotlin: 'this' in member functions only, extension functions use receiver. JavaScript: 'this' binding is complex, arrow functions inherit outer 'this'. EK9: 'this' strictly limited to class/record methods, constructors, and operators. E05070 if used elsewhere.","keywords":["E05070","circular","class","context","function","hierarchy","inherit","instance","method","migrate","scope","standalone","this","type"],"primaryTopics":[],"typicalErrors":[{"error":"E50001","correct":"doubleValue() as pure\n      -> inputValue as Integer\n      <- result as Integer: inputValue * 2","incorrect":"doubleValue() as pure\n      -> inputValue as Integer\n      <- result as Integer: this.count * 2","explanation":"Standalone functions have no instance, so the this keyword is meaningless. Using this in a defines function context triggers E50001. See ek9 -h E50001 for details."},{"error":"E50060","correct":"require counter.currentCount() == 11","incorrect":"require counter.currentCount().intValue() == 11","explanation":"Integer has no intValue() method in EK9. Integer values are used directly without conversion methods. See ek9 -h E50060 for details."}],"companions":[]}
{"id":606,"category":"Type Hierarchy Constraints","question":"What is method shadowing and how does EK9 prevent it?","url":"https://ek9.io/qa/QA0606.html","alternatePhrasings":["What is E05120 in EK9?","How does EK9 detect method shadowing?","Why can't I have a method with the same name but different signature in a child class?"],"answer":"Method shadowing occurs when a child class declares a method with the same name as a parent method but a different signature (different parameter types or count). This creates confusing behavior where calling through a parent reference invokes the parent method, but calling through a child reference invokes the child method.\n\nWHY SHADOWING IS DANGEROUS\nShadowing violates the substitution principle. If child.process() does something completely different from parent.process(), code expecting parent behavior through a parent-typed reference will behave differently from code using a child-typed reference. This is a major source of bugs.\n\nE05120: SHADOWING DETECTION\nEK9 detects shadowing and raises E05120. If a child method has the same name as a parent method but a different signature, it's not a valid override (signatures don't match) and it's not a new method (name collision). The compiler rejects it.\n\nWHAT TO DO INSTEAD\nIf replacing parent behavior: use 'override' with the EXACT same signature. If creating a new method: rename it to avoid collision with the parent method name. If you need both behaviors: keep both methods but give them different, descriptive names.\n\nOVERRIDE VS SHADOW\nA proper override uses the 'override' keyword with an identical signature. A shadow accidentally matches the name without matching the signature. EK9 requires explicit intent via 'override', preventing accidental shadowing.\n\nSee Q570 for override basics. See Q571 for missing override keyword. See Q572 for false override claims.","ek9Example":"defines module qa.typehierarchy.methodshadow\n\n  defines class\n\n    Formatter as open\n      format() as pure\n        -> text as String\n        <- rtn as String: text\n\n      describe() as pure\n        <- rtn as String: \"base formatter\"\n\n      default operator ?\n\n    UpperFormatter extends Formatter\n      //CORRECT: override with exact same signature\n      override format() as pure\n        -> text as String\n        <- rtn as String: text.upperCase()\n\n      override describe() as pure\n        <- rtn as String: \"upper formatter\"\n\n      //CORRECT: new method with a different name (no collision)\n      formatWithPrefix() as pure\n        -> text as String\n        <- rtn as String: \">> \" + text.upperCase()\n\n      default operator ?\n\n  defines function\n\n    testProperOverride()\n      base <- Formatter()\n      upper <- UpperFormatter()\n\n      require base.format(\"hello\") == \"hello\"\n      require upper.format(\"hello\") == \"HELLO\"\n      require upper.formatWithPrefix(\"hello\") == \">> HELLO\"\n\n      //Polymorphic: calling through parent type still dispatches correctly\n      formatters <- List() of Formatter\n      formatters += Formatter()\n      formatters += UpperFormatter()\n\n      for formatter in formatters\n        result <- formatter.format(\"test\")\n        require result?","migrationContext":"Java: shadowing is allowed (with @Override catching some cases, but not all). C++: hiding is allowed by default, 'override' keyword optional. C#: 'new' keyword can explicitly shadow (EK9 bans this entirely). Python: no override concept, last definition wins. Kotlin: 'override' required, similar to EK9. Swift: 'override' required, similar to EK9. EK9: shadowing is a compile error (E05120), override keyword required for intentional replacement.","keywords":["E05120","abstract","child","circular","collision","function","hierarchy","inherit","name","open","override","parent","shadow","shadowing","signature","substitution","type","virtual"],"primaryTopics":[],"typicalErrors":[{"error":"E05120","correct":"override format() as pure","incorrect":"format() as pure","explanation":"When the child UpperFormatter has a method matching parent Formatter.format, the override keyword is required. Without it, the compiler detects method shadowing. See ek9 -h E05120 for details."},{"error":"E05110","correct":"formatWithPrefix() as pure","incorrect":"override formatWithPrefix() as pure","explanation":"Adding override to formatWithPrefix would be a false claim since Formatter has no method named formatWithPrefix. The compiler rejects override when no parent method matches. See ek9 -h E05110 for details."}],"companions":[]}
{"id":607,"category":"Type Hierarchy Constraints","question":"Must a class with 'allow only' be declared as open?","url":"https://ek9.io/qa/QA0607.html","alternatePhrasings":["What is E05270 in EK9?","Why does 'allow only' require 'as open'?","What happens if a sealed class is not open?"],"answer":"Yes. A class that uses 'allow only' to restrict its permitted subclasses MUST be declared 'as open'. If a class has 'allow only' but is not 'as open', the compiler raises E05270.\n\nWHY OPEN IS REQUIRED\nThe 'allow only' clause specifies which subclasses may extend this class. But if the class is not 'as open', no class can extend it at all. Having 'allow only' on a closed class is contradictory: you're listing permitted subclasses for a class that permits no subclasses.\n\nCORRECT PATTERN\nAlways combine 'allow only' with 'as open':\n  Shape allow only Circle, Square as open\n    area() as abstract\nThis declares Shape as extensible but only by Circle and Square.\n\nTRAITS ARE DIFFERENT\nTraits are inherently open (they must be implementable). The E05270 check applies specifically to classes, which are closed by default in EK9.\n\nABSTRACT CLASSES\nAbstract classes are implicitly open (they must be extensible to be useful). So 'abstract' classes with 'allow only' don't need an explicit 'as open' modifier.\n\nSee Q298 for allow only basics. See Q300 for abstract exemption in allow only lists. See Q301 for sealed class basics. See Q608 for sealed classes vs sealed traits. See Q101 for closed-by-default philosophy.","ek9Example":"defines module qa.typehierarchy.sealedopen\n\n  defines class\n\n    //CORRECT: 'allow only' combined with 'as open'\n    Shape allow only Circle, Square as open\n      name()\n        <- rtn as String: \"shape\"\n\n    Circle extends Shape\n      override name()\n        <- rtn as String: \"circle\"\n\n    Square extends Shape\n      override name()\n        <- rtn as String: \"square\"\n\n  defines class\n\n    //CORRECT: abstract class with 'allow only' (abstract is implicitly open)\n    Event allow only ClickEvent, KeyEvent as abstract\n      eventType() as abstract\n        <- rtn as String?\n      default operator ?\n\n    ClickEvent extends Event\n      override eventType()\n        <- rtn as String: \"click\"\n      default operator ?\n\n    KeyEvent extends Event\n      override eventType()\n        <- rtn as String: \"key\"\n      default operator ?\n\n  defines function\n\n    testSealedOpenClasses()\n      circle <- Circle()\n      square <- Square()\n      require circle.name() == \"circle\"\n      require square.name() == \"square\"\n\n    testSealedAbstractClasses()\n      click <- ClickEvent()\n      key <- KeyEvent()\n      require click.eventType() == \"click\"\n      require key.eventType() == \"key\"","migrationContext":"Java: sealed classes are implicitly extensible by permitted types (no separate 'open' keyword needed). Kotlin: sealed classes are abstract by default. C#: no equivalent. Rust: no class sealing. EK9: sealed classes with 'allow only' must explicitly be 'as open' (E05270), because classes are closed by default.","keywords":["E05270","allow","circular","class","closed","define","exhaustive","extend","hierarchy","only","open","sealed","subclass","type"],"primaryTopics":[],"typicalErrors":[{"error":"E05270","correct":"Shape allow only Circle, Square as open","incorrect":"Shape allow only Circle, Square","explanation":"A class with allow only must be declared as open. Without as open, the class is closed by default and no subclasses can extend it, making the allow only list contradictory. See ek9 -h E05270 for details."},{"error":"E05120","correct":"override name()","incorrect":"name()","explanation":"When overriding the name method from the sealed parent Shape, the override keyword is mandatory. Omitting it triggers shadowing detection. See ek9 -h E05120 for details."}],"companions":[]}
{"id":608,"category":"Type Hierarchy Constraints","question":"How do sealed classes differ from sealed traits in EK9?","url":"https://ek9.io/qa/QA0608.html","alternatePhrasings":["Should I use a sealed class or a sealed trait?","What is the difference between allow only on classes and traits?","When to seal a class vs a trait in EK9?"],"answer":"Both classes and traits support 'allow only' to create sealed hierarchies, but they differ in requirements and use cases.\n\nSEALED CLASSES\nSealed classes require 'as open' (or 'as abstract') because classes are closed by default. Sealed classes use single inheritance: each permitted subclass extends exactly one sealed parent. This creates a clean, linear hierarchy well-suited for data types.\n\nSEALED TRAITS\nTraits are inherently open (they must be implementable), so they don't need 'as open'. Sealed traits allow multiple implementation: a class can implement multiple sealed traits. This creates richer type relationships suited for behavioral contracts.\n\nE05240 AND E05270\nBoth sealed classes and traits enforce E05240 (unpermitted concrete type tries to implement/extend). Sealed classes additionally enforce E05270 if they forget 'as open'. Traits never get E05270 because traits are always implementable.\n\nWHEN TO USE EACH\nUse sealed classes when you want a fixed set of data variants (like Result, Optional). Use sealed traits when you want a fixed set of behavioral capabilities that classes can mix.\n\nDISPATCHER EXHAUSTIVENESS\nBoth sealed classes and sealed traits trigger exhaustive dispatch checking (E05260). A dispatcher over either type must handle all permitted types.\n\nSee Q298 for sealed traits. See Q301 for sealed classes. See Q607 for E05270. See Q302 for dispatchers with sealed classes.","ek9Example":"defines module qa.typehierarchy.sealedcomparison\n\n  defines trait\n\n    //Sealed trait: no 'as open' needed (traits are inherently open)\n    Printable allow only Document, Spreadsheet, Slide\n      render() as abstract\n        <- rtn as String?\n\n  defines class\n\n    //Sealed class: requires 'as open' explicitly\n    Vehicle allow only Car, Truck as open\n      describe()\n        <- rtn as String: \"vehicle\"\n\n    Car extends Vehicle\n      override describe()\n        <- rtn as String: \"car\"\n\n    Truck extends Vehicle\n      override describe()\n        <- rtn as String: \"truck\"\n\n    //Classes implementing the sealed trait\n    Document with trait of Printable\n      override render()\n        <- rtn as String: \"document\"\n\n    Spreadsheet with trait of Printable\n      override render()\n        <- rtn as String: \"spreadsheet\"\n\n    Slide with trait of Printable\n      override render()\n        <- rtn as String: \"slide\"\n\n  defines function\n\n    testSealedClass()\n      car <- Car()\n      truck <- Truck()\n      require car.describe() == \"car\"\n      require truck.describe() == \"truck\"\n\n    testSealedTrait()\n      doc <- Document()\n      sheet <- Spreadsheet()\n      slide <- Slide()\n      require doc.render() == \"document\"\n      require sheet.render() == \"spreadsheet\"\n      require slide.render() == \"slide\"","migrationContext":"Java: sealed interfaces and sealed classes have similar distinction (Java 17+). Kotlin: sealed classes and sealed interfaces with permits. Scala: sealed traits and sealed abstract classes. Rust: enums (similar to sealed classes), no sealed traits. EK9: sealed classes require 'as open', sealed traits inherently open, both support exhaustive dispatch.","keywords":["E05240","E05270","allow","circular","class","closed","difference","exhaustive","hierarchy","inherit","multiple","only","sealed","single","trait","type"],"primaryTopics":[],"typicalErrors":[{"error":"E05270","correct":"Vehicle allow only Car, Truck as open","incorrect":"Vehicle allow only Car, Truck","explanation":"Sealed classes require as open because classes are closed by default. Without it, the allow only list is contradictory since no class can extend a closed type. See ek9 -h E05270 for details."},{"error":"E05120","correct":"override describe()","incorrect":"describe()","explanation":"When overriding the describe method from the sealed parent Vehicle, the override keyword is mandatory. Omitting it triggers shadowing detection. See ek9 -h E05120 for details."}],"companions":[]}
{"id":609,"category":"Type Hierarchy Constraints","question":"Can sealed enforcement work across module boundaries?","url":"https://ek9.io/qa/QA0609.html","alternatePhrasings":["Does allow only work across EK9 modules?","How does EK9 enforce sealed types in different modules?","Are sealed constraints checked across packages?"],"answer":"Yes, sealed enforcement (E05240) works across module boundaries. The 'allow only' list names specific concrete types, and the compiler checks every concrete class that attempts to extend or implement the sealed type, regardless of which module it is in.\n\nTRANSITIVE CHECKING\nThe check is transitive. If Module A defines a sealed trait and Module B defines a class implementing it, the compiler still verifies that Module B's class is in the 'allow only' list. If not, E05240 fires.\n\nWHY CROSS-MODULE WORKS\nEK9 compiles all referenced modules together. The full type hierarchy is available during TYPE_HIERARCHY_CHECKS, so the compiler has complete visibility across module boundaries.\n\nLIMITATIONS\nThe permitted types must be resolvable from the sealed type's module. This means they must be in the same module or in a module that is a dependency. You cannot list a type from a module that the sealed type's module doesn't know about.\n\nABSTRACT INTERMEDIARIES\nAbstract classes can implement a sealed trait from any module without being in the 'allow only' list. Only concrete subclasses need to be listed.\n\nSee Q298 for sealed trait basics. See Q300 for abstract exemptions. See Q607 for sealed class requirements. See Q6 for module organization.","ek9Example":"defines module qa.typehierarchy.crossmodule\n\n  defines trait\n\n    //Sealed trait with all permitted types defined in same module\n    //In practice, permitted types could be in different modules\n    Serializable allow only JsonFormat, XmlFormat, CsvFormat\n      serialize() as abstract\n        <- rtn as String?\n\n  defines class\n\n    JsonFormat with trait of Serializable\n      override serialize()\n        <- rtn as String: \"json\"\n\n    XmlFormat with trait of Serializable\n      override serialize()\n        <- rtn as String: \"xml\"\n\n    CsvFormat with trait of Serializable\n      override serialize()\n        <- rtn as String: \"csv\"\n\n  defines function\n\n    testSealedCrossModule()\n      formats <- List() of Serializable\n      formats += JsonFormat()\n      formats += XmlFormat()\n      formats += CsvFormat()\n\n      for fmt in formats\n        result <- fmt.serialize()\n        require result?","migrationContext":"Java: sealed interfaces work across packages but within the same module. Kotlin: sealed hierarchy restricted to same package/module. Rust: orphan rule restricts trait implementation to same crate. EK9: sealed enforcement works across modules with full transitive checking.","keywords":["E05240","boundary","circular","closed","cross","enforcement","exhaustive","hierarchy","inherit","module","package","sealed","transitive","type","visibility"],"primaryTopics":[],"typicalErrors":[{"error":"E50060","correct":"result <- fmt.serialize()","incorrect":"result <- fmt.serialize().toUpperCase()","explanation":"String has no toUpperCase() method in EK9. Use upperCase() instead. See ek9 -h E50060 for details."},{"error":"E05120","correct":"override serialize()","incorrect":"serialize()","explanation":"When implementing the abstract serialize method from the sealed Serializable trait, the override keyword is required. Omitting it triggers shadowing detection. See ek9 -h E05120 for details."}],"companions":[]}
{"id":610,"category":"Type Hierarchy Constraints","question":"How do I refactor circular dependencies using composition?","url":"https://ek9.io/qa/QA0610.html","alternatePhrasings":["How to fix circular hierarchy errors in EK9?","What is the composition pattern for breaking cycles?","How do I restructure code that has circular inheritance?"],"answer":"When you encounter E05020 (circular hierarchy), the root cause is usually two types that each depend on the other's features. The solution is composition: instead of A extending B and B extending A, extract the shared behavior and have both types delegate to it.\n\nEXTRACT SHARED BEHAVIOR\nIdentify what A needs from B and what B needs from A. Create a separate type (trait or class) for the shared behavior. Both A and B depend on the extracted type instead of on each other.\n\nUSE TRAITS FOR CONTRACTS\nIf A and B need to call methods on each other, define traits that describe the required interface. Each class implements the trait the other needs, breaking the direct dependency.\n\nDELEGATE INSTEAD OF INHERIT\nInstead of inheriting methods, hold a reference to the other object and call methods through delegation. This is the classic 'composition over inheritance' principle.\n\nPRACTICAL EXAMPLE\nIf Engine needs Car features and Car needs Engine features, extract a Powertrain trait. Engine implements its contract, Car implements its contract, and they communicate through the trait interface rather than inheritance.\n\nSee Q602 for circular hierarchy basics. See Q109 for composition patterns. See Q212 for composition over inheritance. See Q210 for trait delegation.","ek9Example":"defines module qa.typehierarchy.composition\n\n  defines trait\n\n    //Shared behavior extracted into traits\n    Reportable\n      report() as abstract\n        <- rtn as String?\n\n    Trackable\n      tracking() as abstract\n        <- rtn as String?\n\n  defines class\n\n    //Instead of Order extending Shipment extending Order (circular),\n    //both implement shared traits and use composition\n\n    OrderItem with trait of Reportable\n      itemName <- \"item\"\n      shipmentRef <- String()\n\n      OrderItem()\n        ->\n          itemName as String\n          shipmentRef as String\n        this.itemName: itemName\n        this.shipmentRef: shipmentRef\n\n      override report()\n        <- rtn as String: `Order: ${itemName}`\n\n      shipmentReference() as pure\n        <- rtn as String: shipmentRef\n\n      default operator ?\n\n    Shipment with trait of Trackable\n      shipmentId <- \"shipment\"\n      orderRef <- String()\n\n      Shipment()\n        ->\n          shipmentId as String\n          orderRef as String\n        this.shipmentId: shipmentId\n        this.orderRef: orderRef\n\n      override tracking()\n        <- rtn as String: `Shipment: ${shipmentId}`\n\n      orderReference() as pure\n        <- rtn as String: orderRef\n\n      default operator ?\n\n  defines function\n\n    testCompositionOverCycles()\n      //OrderItem and Shipment reference each other by ID, not by inheritance\n      item <- OrderItem(\"Widget\", \"SHIP-001\")\n      shipment <- Shipment(\"SHIP-001\", \"ORD-100\")\n\n      require item.report() == \"Order: Widget\"\n      require shipment.tracking() == \"Shipment: SHIP-001\"\n      require item.shipmentReference() == \"SHIP-001\"\n      require shipment.orderReference() == \"ORD-100\"","migrationContext":"Java: same refactoring pattern applies (extract interface, use composition). Python: mixins or composition to break cycles. Rust: traits for shared behavior, no inheritance. Go: interfaces and embedding for composition. Kotlin: delegation pattern with 'by' keyword. EK9: use traits and composition to replace circular inheritance.","keywords":["E05020","circular","composition","delegate","dependency","extract","hierarchy","inheritance","refactor","trait","type"],"primaryTopics":[],"typicalErrors":[{"error":"E05030","correct":"OrderItem with trait of Reportable","incorrect":"OrderItem extends Shipment","explanation":"If OrderItem extended Shipment and Shipment extended OrderItem, a circular hierarchy would be created. Using composition with traits avoids this cycle. See ek9 -h E05030 for details."},{"error":"E05120","correct":"override report()","incorrect":"report()","explanation":"When implementing the abstract report method from the Reportable trait, the override keyword is required. Omitting it triggers shadowing detection. See ek9 -h E05120 for details."}],"companions":[]}
{"id":611,"category":"Type Hierarchy Constraints","question":"What is the full set of type hierarchy checks EK9 performs?","url":"https://ek9.io/qa/QA0611.html","alternatePhrasings":["What hierarchy errors can EK9 detect?","How does EK9 validate type hierarchies?","What are all the E05xxx hierarchy error codes?"],"answer":"EK9 performs comprehensive type hierarchy validation during compilation, catching structural errors that would cause runtime failures in less strict languages.\n\nCIRCULAR HIERARCHIES (E05020)\nDetects cycles in inheritance chains at any depth. A class that directly or indirectly extends itself is rejected. Applies to classes, traits, records, and components.\n\nCLOSED TYPE EXTENSION (E05030)\nEK9 types are closed by default. Extending a type that is not 'as open' or 'as abstract' produces E05030.\n\nTHIS IN WRONG CONTEXT (E05070)\nUsing 'this' in a standalone function or other context without an instance is rejected with E05070.\n\nOVERRIDE VALIDATION (E05100-E05120)\nE05100: claiming override when nothing to override exists. E05110: matching a parent method without 'override' keyword. E05120: method name matches parent but signature differs (shadowing).\n\nPURITY HIERARCHY (E05150-E05160)\nE05150: overriding a pure method without marking the override as pure. E05160: super call from a pure method to a non-pure parent.\n\nSEALED TYPE ENFORCEMENT (E05240-E05270)\nE05240: concrete type not in 'allow only' list. E05250: abstract type in 'allow only' list. E05260: dispatcher missing handler for permitted type. E05270: sealed class not declared 'as open'.\n\nSee Q602 for circular hierarchies. See Q605 for 'this' context. See Q606 for method shadowing. See Q607 for sealed class requirements. See Q570 for override mechanics.\nSee Q679 for sealed allow-only. See Q680 for diamond trait resolution. See Q697 for inheritance depth limit.\nSee Q729 for inheritance depth boundary example.","ek9Example":"defines module qa.typehierarchy.summary\n\n  defines trait\n\n    //Trait hierarchy: clean and valid\n    Loggable\n      logEntry() as abstract\n        <- rtn as String?\n\n  defines class\n\n    //Open class: can be extended\n    BaseEntity as open\n      entityId()\n        <- rtn as String: \"entity\"\n      default operator ?\n\n    //Extends open class: valid\n    UserEntity extends BaseEntity with trait of Loggable\n      override entityId()\n        <- rtn as String: \"user\"\n      override logEntry()\n        <- rtn as String: \"User entity\"\n      default operator ?\n\n    //Sealed class: open + allow only\n    Status allow only Active, Inactive as open\n      label()\n        <- rtn as String: \"status\"\n\n    Active extends Status\n      override label()\n        <- rtn as String: \"active\"\n\n    Inactive extends Status\n      override label()\n        <- rtn as String: \"inactive\"\n\n  defines function\n\n    testHierarchySummary()\n      user <- UserEntity()\n      require user.entityId() == \"user\"\n      require user.logEntry() == \"User entity\"\n\n      active <- Active()\n      inactive <- Inactive()\n      require active.label() == \"active\"\n      require inactive.label() == \"inactive\"","migrationContext":"Java: some checks at compile time (circular, override), others at runtime. Python: MRO failures at class creation time. C++: compile-time hierarchy checks but no sealed types. Kotlin: comprehensive compile-time checks similar to EK9. Rust: no inheritance, trait coherence rules instead. EK9: all hierarchy checks at compile time, no runtime surprises.","keywords":["E05020","E05030","E05070","E05120","E05240","E05270","check","circular","compile","hierarchy","inherit","summary","type","validation"],"primaryTopics":[],"typicalErrors":[{"error":"E05270","correct":"Status allow only Active, Inactive as open","incorrect":"Status allow only Active, Inactive","explanation":"A sealed class with allow only must be declared as open. Without as open, the class is closed by default and no subclasses can extend it. See ek9 -h E05270 for details."},{"error":"E05120","correct":"override entityId()","incorrect":"entityId()","explanation":"When overriding entityId from the parent BaseEntity, the override keyword is mandatory. Without it, the compiler detects method shadowing. See ek9 -h E05120 for details."},{"error":"E50060","correct":"require user.entityId() == \"user\"","incorrect":"require user.entityId().toUpperCase() == \"user\"","explanation":"String has no toUpperCase() method in EK9. Use upperCase() instead. See ek9 -h E50060 for details."}],"companions":[]}
{"id":612,"category":"Dispatcher Validation","question":"What happens when a dispatcher entry and handler have different purity?","url":"https://ek9.io/qa/QA0612.html","alternatePhrasings":["What is E05170 in EK9?","Must dispatcher handlers match purity of the entry method?","Can a pure dispatcher have non-pure handlers?"],"answer":"All dispatcher handlers must match the purity of the dispatcher entry method. If the entry is 'as pure', every handler must also be 'as pure'. If the entry is non-pure, handlers must be non-pure. A mismatch raises E05170.\n\nWHY PURITY MUST MATCH\nCallers of the dispatcher expect consistent behavior. If a dispatcher is declared pure, the caller relies on that guarantee for all possible dispatch targets. A non-pure handler would violate that contract at runtime, depending on which type is passed.\n\nPURE DISPATCHER PATTERN\nMark both the entry and all handlers as pure:\n  describe() as pure dispatcher\n    -> item as Item\n    <- rtn as String: $item\n  describe() as pure\n    -> item as Book\n    <- rtn as String: \"Book: \" + item.title()\n\nNON-PURE DISPATCHER PATTERN\nOmit 'as pure' from both entry and handlers:\n  process() as dispatcher\n    -> item as Item\n  process()\n    -> item as Book\n    stdout.println(item.title())\n\nMIXING IS FORBIDDEN\nYou cannot have a pure entry with a non-pure handler, or vice versa. The compiler checks every handler against the entry's purity.\n\nSee Q560 for purity basics. See Q566 for pure method restrictions. See Q60 for dispatcher fundamentals. See Q620 for dispatcher hierarchy rules.","ek9Example":"defines module qa.dispatchervalidation.purity\n\n  defines class\n\n    Item as abstract\n      name() as pure abstract\n        <- rtn as String?\n      default operator ?\n\n    Book extends Item\n      title <- \"untitled\"\n\n      Book()\n        -> title as String\n        this.title: title\n\n      override name() as pure\n        <- rtn as String: title\n\n      default operator ?\n\n    Magazine extends Item\n      issue <- \"unknown\"\n\n      Magazine()\n        -> issue as String\n        this.issue: issue\n\n      override name() as pure\n        <- rtn as String: issue\n\n      default operator ?\n\n    //CORRECT: pure dispatcher with all pure handlers\n    PureCatalog\n      describe() as pure dispatcher\n        -> item as Item\n        <- rtn as String: item.name()\n\n      describe() as pure\n        -> item as Book\n        <- rtn as String: \"Book: \" + item.name()\n\n      describe() as pure\n        -> item as Magazine\n        <- rtn as String: \"Magazine: \" + item.name()\n\n  defines function\n\n    testPureDispatcher()\n      catalog <- PureCatalog()\n      book <- Book(\"EK9 Guide\")\n      mag <- Magazine(\"Tech Monthly\")\n\n      bookResult <- catalog.describe(book)\n      magResult <- catalog.describe(mag)\n\n      require bookResult == \"Book: EK9 Guide\"\n      require magResult == \"Magazine: Tech Monthly\"","migrationContext":"Java: no purity concept, no dispatch purity checking. Kotlin: no purity enforcement on dispatchers. Haskell: all functions pure by default, dispatch via type classes. Rust: trait methods can be effectful or pure, but no dispatch purity contract. EK9: E05170 enforces consistent purity across all dispatcher handlers.","keywords":["E05170","ambiguity","consistent","contract","dispatch","dispatcher","entry","handler","immutable","match","pure","purity","side-effect","validate"],"primaryTopics":[],"typicalErrors":[{"error":"E05170","correct":"describe() as pure\n        -> item as Book","incorrect":"describe()\n        -> item as Book","explanation":"The dispatcher entry describe is declared as pure. Every handler must also be pure. Omitting as pure on a handler creates a purity mismatch. See ek9 -h E05170 for details."},{"error":"E50010","correct":"describe() as pure\n        -> item as Magazine","incorrect":"describe() as pure\n        -> vehicle as Vehicle","explanation":"Each handler parameter type must be a subtype of the dispatcher entry parameter type Item. A handler for an unrelated type like Vehicle is outside the hierarchy. See ek9 -h E50010 for details."}],"companions":[]}
{"id":613,"category":"Dispatcher Validation","question":"Can a derived class dispatcher target a private handler in the parent?","url":"https://ek9.io/qa/QA0613.html","alternatePhrasings":["What is E05180 in EK9?","Why can't a dispatcher call private methods from a parent?","What access level do dispatcher handlers need?"],"answer":"No. A dispatcher cannot target a private method from a parent class. Private methods are not visible to child classes, so dispatch to them would fail. The compiler catches this with E05180.\n\nPRIVATE METHODS ARE INVISIBLE\nWhen a child class defines a dispatcher, the dispatch targets must be accessible. Private methods in the parent are not inherited by children and cannot be dispatched to.\n\nCORRECT ACCESS LEVELS\nDispatcher handlers should be protected (visible to children) or public (visible to all). If the handler is in the same class as the dispatcher, private is fine because the dispatcher and handler are in the same scope.\n\nSAME-CLASS DISPATCHERS\nWhen both dispatcher entry and handlers are in the same class, private handlers are perfectly valid. The E05180 check only fires when the dispatcher in a child class tries to reach a private method in a parent.\n\nWHAT TO DO\nIf a parent method needs to be a dispatch target from child classes, change it from private to protected. If the method must remain private, move the dispatcher into the parent class.\n\nSee Q60 for dispatcher fundamentals. See Q95 for field visibility. See Q573 for access modifier rules on overrides. See Q614 for dispatcher type hierarchy validation.","ek9Example":"defines module qa.dispatchervalidation.access\n\n  defines class\n\n    Message as abstract\n      content() as pure abstract\n        <- rtn as String?\n      default operator ?\n\n    TextMessage extends Message\n      body <- \"text\"\n\n      TextMessage()\n        -> body as String\n        this.body: body\n\n      override content() as pure\n        <- rtn as String: body\n\n      default operator ?\n\n    AlertMessage extends Message\n      alert <- \"alert\"\n\n      AlertMessage()\n        -> alert as String\n        this.alert: alert\n\n      override content() as pure\n        <- rtn as String: alert\n\n      default operator ?\n\n    //CORRECT: Dispatcher and all handlers in same class\n    MessageProcessor\n\n      process() as dispatcher\n        -> msg as Message\n        <- rtn as String: msg.content()\n\n      process()\n        -> msg as TextMessage\n        <- rtn as String: \"Text: \" + msg.content()\n\n      process()\n        -> msg as AlertMessage\n        <- rtn as String: \"Alert: \" + msg.content()\n\n  defines function\n\n    testSameClassDispatcher()\n      processor <- MessageProcessor()\n\n      textMsg <- TextMessage(\"hello\")\n      alertMsg <- AlertMessage(\"warning\")\n\n      require processor.process(textMsg) == \"Text: hello\"\n      require processor.process(alertMsg) == \"Alert: warning\"","migrationContext":"Java: method overloading resolution doesn't access private methods from child. C++: virtual dispatch doesn't apply to private methods. Python: no private methods (name mangling is convention). Kotlin: private methods invisible to subclasses. EK9: E05180 prevents dispatcher from targeting private parent methods.","keywords":["E05180","access","ambiguity","child","class","dispatch","dispatcher","handler","parent","private","protected","validate","visible"],"primaryTopics":[],"typicalErrors":[{"error":"E50060","correct":"process()\n        -> msg as TextMessage","incorrect":"process()\n        -> msg as String","explanation":"The dispatcher entry parameter is Message. Every handler parameter must be a subtype of Message. String is not in the Message hierarchy. See ek9 -h E50060 for details."},{"error":"E05220","correct":"process()\n        -> msg as AlertMessage\n        <- rtn as String: \"Alert: \" + msg.content()","incorrect":"process()\n        -> msg as AlertMessage\n        <- rtn as Integer: 42","explanation":"All dispatcher handlers must return the same type as the entry method. The entry returns String, so a handler returning Integer is incompatible. See ek9 -h E05220 for details."},{"error":"E07820","correct":"      process()\n        -> msg as TextMessage\n        <- rtn as String: \"Text: \" + msg.content()","incorrect":"      process() as dispatcher\n        -> msg as TextMessage\n        <- rtn as String: \"Text: \" + msg.content()","explanation":"Only one method with a given name can be marked 'as dispatcher'. The entry point process() is already marked as dispatcher. Marking a handler method as dispatcher too triggers E07820. See ek9 -h E07820 for details."}],"companions":[]}
{"id":614,"category":"Dispatcher Validation","question":"What happens if a dispatcher handler's parameter type is outside the hierarchy?","url":"https://ek9.io/qa/QA0614.html","alternatePhrasings":["What is E05210 in EK9?","Why must dispatcher handler types be in the same hierarchy?","Can a dispatcher handle unrelated types?"],"answer":"Every dispatcher handler's parameter type must be a subtype of the dispatcher entry's parameter type. If a handler specifies a type that is not in the hierarchy, the compiler raises E05210.\n\nWHY TYPE HIERARCHY MATTERS\nDispatchers route calls based on runtime type. The entry method declares a base type (e.g., Animal). Each handler declares a specific subtype (e.g., Dog, Cat). At runtime, the dispatcher checks the actual type and routes to the matching handler. If a handler type is not in the hierarchy, it can never match.\n\nEXAMPLE OF THE ERROR\nIf the dispatcher entry takes Animal and a handler takes Vehicle, no Animal can ever be a Vehicle. The handler is unreachable and indicates a design error.\n\nCORRECT PATTERN\nAll handlers must use subtypes of the entry's parameter type:\n  handle() as dispatcher\n    -> a as Animal\n  handle()\n    -> a as Dog        //OK: Dog extends Animal\n  handle()\n    -> a as Cat         //OK: Cat extends Animal\n\nSEPARATE DISPATCHERS FOR SEPARATE HIERARCHIES\nIf you need to dispatch on multiple unrelated type hierarchies, create separate dispatchers. Each dispatcher handles one hierarchy.\n\nSee Q60 for dispatcher fundamentals. See Q615 for return type matching. See Q619 for two-parameter dispatchers.","ek9Example":"defines module qa.dispatchervalidation.hierarchy\n\n  defines class\n\n    Animal as abstract\n      speak() as pure abstract\n        <- rtn as String?\n      default operator ?\n\n    Dog extends Animal\n      override speak() as pure\n        <- rtn as String: \"woof\"\n      default operator ?\n\n    Cat extends Animal\n      override speak() as pure\n        <- rtn as String: \"meow\"\n      default operator ?\n\n    Bird extends Animal\n      override speak() as pure\n        <- rtn as String: \"tweet\"\n      default operator ?\n\n    //CORRECT: all handler types are in the Animal hierarchy\n    AnimalHandler\n      handle() as dispatcher\n        -> animal as Animal\n        <- rtn as String: \"Animal: \" + animal.speak()\n\n      handle()\n        -> animal as Dog\n        <- rtn as String: \"Dog says: \" + animal.speak()\n\n      handle()\n        -> animal as Cat\n        <- rtn as String: \"Cat says: \" + animal.speak()\n\n      handle()\n        -> animal as Bird\n        <- rtn as String: \"Bird says: \" + animal.speak()\n\n  defines function\n\n    testHierarchyDispatch()\n      handler <- AnimalHandler()\n\n      dog <- Dog()\n      cat <- Cat()\n      bird <- Bird()\n\n      require handler.handle(dog) == \"Dog says: woof\"\n      require handler.handle(cat) == \"Cat says: meow\"\n      require handler.handle(bird) == \"Bird says: tweet\"","migrationContext":"Java: method overloading doesn't check hierarchy, resolved at compile time. Kotlin: no runtime dispatcher, uses when + is checks. Rust: match on enum variants (closed hierarchy). Go: type switch operates on interfaces. EK9: E05210 enforces that all dispatcher handler parameter types are within the dispatch hierarchy.","keywords":["E05210","ambiguity","dispatch","dispatcher","extends","handler","hierarchy","parameter","subtype","type","unrelated","validate"],"primaryTopics":[],"typicalErrors":[{"error":"E50010","correct":"handle()\n        -> animal as Dog","incorrect":"handle()\n        -> vehicle as Vehicle","explanation":"The dispatcher entry takes Animal. A handler parameter type must be a subtype of Animal. Vehicle is completely unrelated and would never match at runtime. See ek9 -h E50010 for details."},{"error":"E05220","correct":"handle()\n        -> animal as Cat\n        <- rtn as String: \"Cat says: \" + animal.speak()","incorrect":"handle()\n        -> animal as Cat\n        <- rtn as Integer: 0","explanation":"The dispatcher entry returns String. All handlers must return the same type. A handler returning Integer would create an inconsistent return type. See ek9 -h E05220 for details."}],"companions":[]}
{"id":615,"category":"Dispatcher Validation","question":"How does EK9 check dispatcher return type compatibility?","url":"https://ek9.io/qa/QA0615.html","alternatePhrasings":["What is E05220 in EK9?","Must all dispatcher handlers return the same type?","Can dispatcher handlers have different return types?"],"answer":"All dispatcher handlers must return the same type as the dispatcher entry method (or a covariant subtype). If a handler returns a different, incompatible type, the compiler raises E05220.\n\nWHY RETURN TYPES MUST MATCH\nCallers invoke the dispatcher without knowing which handler will execute. The return type must be consistent so the caller can use the result regardless of which handler was selected at runtime.\n\nEXACT MATCH\nThe simplest approach is to use the same return type for all handlers:\n  describe() as dispatcher\n    -> shape as Shape\n    <- rtn as String: \"shape\"\n  describe()\n    -> shape as Circle\n    <- rtn as String: \"circle\"\n\nVOID DISPATCHERS\nDispatchers with no return type (void) require all handlers to also have no return type.\n\nWHAT TO DO\nIf all handlers should return the same type: ensure exact match. If you need handler-specific return types: consider restructuring to return a common base type or using a wrapper.\n\nSee Q60 for dispatcher fundamentals. See Q614 for parameter type validation. See Q616 for dispatcher ambiguity.","ek9Example":"defines module qa.dispatchervalidation.returntypes\n\n  defines class\n\n    Shape as abstract\n      name() as pure abstract\n        <- rtn as String?\n      default operator ?\n\n    Circle extends Shape\n      override name() as pure\n        <- rtn as String: \"circle\"\n      default operator ?\n\n    Rectangle extends Shape\n      override name() as pure\n        <- rtn as String: \"rectangle\"\n      default operator ?\n\n    //CORRECT: All handlers return String (matching dispatcher entry)\n    ShapeDescriber\n      describe() as dispatcher\n        -> shape as Shape\n        <- rtn as String: \"Shape: \" + shape.name()\n\n      describe()\n        -> shape as Circle\n        <- rtn as String: \"Circle with round edges\"\n\n      describe()\n        -> shape as Rectangle\n        <- rtn as String: \"Rectangle with four sides\"\n\n    //CORRECT: Void dispatcher (no return type for any handler)\n    ShapePrinter\n      print() as dispatcher\n        -> shape as Shape\n        content <- \"Printing: \" + shape.name()\n        require content?\n\n      print()\n        -> shape as Circle\n        content <- \"Printing circle\"\n        require content?\n\n      print()\n        -> shape as Rectangle\n        content <- \"Printing rectangle\"\n        require content?\n\n  defines function\n\n    testReturnTypeMatch()\n      describer <- ShapeDescriber()\n      circle <- Circle()\n      rect <- Rectangle()\n\n      require describer.describe(circle) == \"Circle with round edges\"\n      require describer.describe(rect) == \"Rectangle with four sides\"\n\n    testVoidDispatcher()\n      printer <- ShapePrinter()\n      printer.print(Circle())\n      printer.print(Rectangle())","migrationContext":"Java: method overloading requires exact return type match (not covariant). Kotlin: when expressions can return a common type. Rust: match arms must return same type. Scala: pattern matching returns common supertype. EK9: E05220 enforces return type compatibility across all dispatcher handlers.","keywords":["E05220","ambiguity","compatible","consistent","covariant","dispatch","dispatcher","handler","match","return","type","validate"],"primaryTopics":[],"typicalErrors":[{"error":"E05220","correct":"describe()\n        -> shape as Circle\n        <- rtn as String: \"Circle with round edges\"","incorrect":"describe()\n        -> shape as Circle\n        <- rtn as Integer: 42","explanation":"The dispatcher entry returns String. A handler returning Integer is incompatible. All handlers must return the same type as the entry method. See ek9 -h E05220 for details."},{"error":"E50010","correct":"describe()\n        -> shape as Rectangle","incorrect":"describe()\n        -> animal as Animal","explanation":"The dispatcher entry dispatches on Shape. A handler for an unrelated type Animal is outside the Shape hierarchy and can never match. See ek9 -h E50010 for details."}],"companions":[]}
{"id":616,"category":"Dispatcher Validation","question":"What is dispatcher ambiguity and how is it detected?","url":"https://ek9.io/qa/QA0616.html","alternatePhrasings":["What is E05230 in EK9?","When does a dispatcher become ambiguous?","How does EK9 resolve dispatcher cost ties?"],"answer":"Dispatcher ambiguity (E05230) occurs when two or more handlers match an argument type at equal cost. EK9 uses a cost-based dispatch model and refuses to guess when costs are tied.\n\nCOST MODEL\nEK9 assigns dispatch costs based on type distance: exact type match costs 0.00, each class inheritance step costs 0.05, and each trait implementation step costs 0.10. The handler with the lowest cost wins.\n\nWHEN AMBIGUITY OCCURS\nAmbiguity arises when a class implements two traits and both traits have handlers at the same cost. Since both trait implementations are at equal distance, the dispatcher cannot choose.\n\nEXAMPLE\nIf Duck implements both Flyable and Swimmable, and the dispatcher has handlers for both traits at the same depth, dispatching a Duck is ambiguous (both handlers cost 0.10).\n\nRESOLUTION STRATEGIES\nStrategy 1 - Explicit handler: add a handler for the ambiguous concrete type (exact match at cost 0.00 wins). Strategy 2 - Bridge trait: create a combining trait that encompasses both competing traits, giving it a shorter path. Strategy 3 - Decomposed dispatchers: split into separate dispatchers, each handling one concern.\n\nEK9 REFUSES TO GUESS\nUnlike languages that pick 'first declared' or 'most recently added', EK9 treats ambiguity as a design error. The compiler requires structural resolution.\n\nSee Q617 for diamond trait ambiguity. See Q60 for dispatcher fundamentals. See Q255 for cost-based method resolution.","ek9Example":"defines module qa.dispatchervalidation.ambiguity\n\n  defines class\n\n    //CORRECT: No ambiguity because only one trait hierarchy is dispatched\n    Vehicle as abstract\n      kind() as pure abstract\n        <- rtn as String?\n      default operator ?\n\n    Car extends Vehicle\n      override kind() as pure\n        <- rtn as String: \"car\"\n      default operator ?\n\n    Truck extends Vehicle\n      override kind() as pure\n        <- rtn as String: \"truck\"\n      default operator ?\n\n    Motorcycle extends Vehicle\n      override kind() as pure\n        <- rtn as String: \"motorcycle\"\n      default operator ?\n\n    //Clear hierarchy: Car, Truck, Motorcycle all extend Vehicle\n    //No ambiguity because single-parent inheritance has clear costs\n    VehicleProcessor\n      process() as dispatcher\n        -> vehicle as Vehicle\n        <- rtn as String: \"Vehicle: \" + vehicle.kind()\n\n      process()\n        -> vehicle as Car\n        <- rtn as String: \"Car: \" + vehicle.kind()\n\n      process()\n        -> vehicle as Truck\n        <- rtn as String: \"Truck: \" + vehicle.kind()\n\n      process()\n        -> vehicle as Motorcycle\n        <- rtn as String: \"Motorcycle: \" + vehicle.kind()\n\n  defines function\n\n    testUnambiguousDispatch()\n      processor <- VehicleProcessor()\n\n      car <- Car()\n      truck <- Truck()\n      moto <- Motorcycle()\n\n      require processor.process(car) == \"Car: car\"\n      require processor.process(truck) == \"Truck: truck\"\n      require processor.process(moto) == \"Motorcycle: motorcycle\"","migrationContext":"Java: method overloading resolved at compile time, no runtime ambiguity concept. C++: multiple inheritance ambiguity resolved with virtual base classes. Python: MRO (C3 linearization) provides deterministic resolution. Kotlin: compiler error for ambiguous overloads. Julia: multiple dispatch with ambiguity detection. EK9: E05230 detects runtime dispatch ambiguity at compile time, requires structural resolution.","keywords":["E05230","ambiguity","bridge","cost","dispatch","dispatcher","explicit","resolution","tie","trait","validate"],"primaryTopics":[],"typicalErrors":[{"error":"E50060","correct":"require processor.process(car) == \"Car: car\"","incorrect":"require processor.process(car).toUpperCase() == \"Car: car\"","explanation":"String has no toUpperCase() method in EK9. Use upperCase() instead. See ek9 -h E50060 for details."},{"error":"E50010","correct":"process()\n        -> vehicle as Truck","incorrect":"process()\n        -> shape as Shape","explanation":"The dispatcher entry takes Vehicle. A handler for Shape is outside the Vehicle hierarchy and can never be reached through dispatch. See ek9 -h E50010 for details."}],"companions":[]}
{"id":617,"category":"Dispatcher Validation","question":"How can trait diamond patterns cause dispatcher ambiguity?","url":"https://ek9.io/qa/QA0617.html","alternatePhrasings":["What is the diamond problem in EK9 dispatchers?","How do multiple trait implementations affect dispatch?","Why does implementing two traits cause E05230?"],"answer":"When a class implements two traits and a dispatcher has handlers for both traits, the dispatch cost to both handlers is identical (0.10 each). This creates ambiguity (E05230) because the compiler cannot determine which handler should win.\n\nTHE DIAMOND PROBLEM\nConsider: Duck implements both Flyable and Swimmable. A dispatcher has handlers for Flyable (cost 0.10) and Swimmable (cost 0.10). When dispatching a Duck, both handlers are equally good matches. This is the diamond problem applied to dispatch.\n\nBRIDGE TRAIT SOLUTION\nCreate a combining trait that encompasses both competing traits:\n  AmphibiousFlyer with trait of Flyable, Swimmable\nThen Duck implements ONLY AmphibiousFlyer (not Flyable and Swimmable directly). The dispatcher handler for AmphibiousFlyer has cost 0.10, while Flyable and Swimmable handlers have cost 0.20 (two hops through the bridge). The bridge handler wins.\n\nCRITICAL: the concrete type must implement ONLY the bridge trait, not the individual traits directly. If Duck implements all three, all handlers are at cost 0.10 and ambiguity remains.\n\nEXPLICIT HANDLER SOLUTION\nAdd a handler for the specific ambiguous type (e.g., Duck) at cost 0.00 (exact match), which beats any trait handler.\n\nSee Q616 for ambiguity basics. See Q60 for dispatcher fundamentals. See Q107 for multiple trait implementation.","ek9Example":"defines module qa.dispatchervalidation.diamond\n\n  defines trait\n\n    //Bridge trait pattern to resolve diamond ambiguity\n    Flyable\n      canFly() as abstract\n        <- rtn as Boolean?\n\n    Swimmable\n      canSwim() as abstract\n        <- rtn as Boolean?\n\n    //Bridge trait: combines both capabilities\n    AmphibiousFlyer with trait of Flyable, Swimmable\n\n  defines class\n\n    //Duck implements ONLY the bridge trait (not Flyable/Swimmable directly)\n    //This gives bridge handler cost 0.10, individual traits cost 0.20\n    Duck with trait of AmphibiousFlyer\n      override canFly()\n        <- rtn as Boolean: true\n      override canSwim()\n        <- rtn as Boolean: true\n\n    //Eagle implements only Flyable\n    Eagle with trait of Flyable\n      override canFly()\n        <- rtn as Boolean: true\n\n    //Fish implements only Swimmable\n    Fish with trait of Swimmable\n      override canSwim()\n        <- rtn as Boolean: true\n\n  defines function\n\n    testBridgeTrait()\n      duck <- Duck()\n      eagle <- Eagle()\n      fish <- Fish()\n\n      require duck.canFly()\n      require duck.canSwim()\n      require eagle.canFly()\n      require fish.canSwim()","migrationContext":"Java: no runtime dispatch, diamond problem handled by interface default method rules. C++: virtual inheritance for diamond problem. Python: C3 linearization (MRO) provides deterministic resolution. Kotlin: explicit override resolution for diamond conflicts. Rust: no class inheritance, trait coherence rules. EK9: E05230 detects diamond dispatch ambiguity, resolved structurally via bridge traits or explicit handlers.","keywords":["E05230","ambiguity","bridge","cost","diamond","dispatch","handler","implement","multiple","sealed","trait","validate","visitor"],"primaryTopics":[],"typicalErrors":[{"error":"E50060","correct":"require duck.canFly()","incorrect":"require duck.canFly().booleanValue()","explanation":"Boolean has no booleanValue() method in EK9. Boolean values are used directly in conditions. See ek9 -h E50060 for details."},{"error":"E05120","correct":"override canFly()","incorrect":"canFly()","explanation":"When implementing a trait abstract method, the override keyword is required. Omitting it on canFly triggers shadowing detection. See ek9 -h E05120 for details."}],"companions":[]}
{"id":618,"category":"Dispatcher Validation","question":"How do sealed dispatchers enforce exhaustive handler coverage?","url":"https://ek9.io/qa/QA0618.html","alternatePhrasings":["What is E05260 for dispatchers?","Must I handle all types in a sealed dispatcher?","How does EK9 enforce exhaustive dispatch on sealed types?"],"answer":"When a dispatcher's parameter type is a sealed type (one with 'allow only'), the compiler requires handlers for ALL permitted concrete types. Missing a handler raises E05260.\n\nEXHAUSTIVE CHECKING\nThe compiler cross-references the 'allow only' list with the set of dispatcher handlers. Every concrete type in the list must have a matching handler (either exact or through a supertype).\n\nCOMPILE-TIME SAFETY\nWhen you add a new type to the 'allow only' list, EVERY dispatcher operating on that type fails to compile until a handler is added. This guarantees all dispatch points are updated when the type hierarchy grows.\n\nUNSEALED DISPATCHERS\nDispatchers on unsealed types (without 'allow only') do NOT require exhaustive handlers. The entry method acts as a catch-all fallback for unhandled types.\n\nABSTRACT TYPES\nAbstract types in the hierarchy do not need handlers because they cannot be instantiated at runtime. Only concrete types require handlers.\n\nBOTH CLASSES AND TRAITS\nExhaustive checking works for both sealed classes (allow only on class) and sealed traits (allow only on trait). The mechanism is identical.\n\nSee Q299 for sealed trait dispatchers. See Q302 for sealed class dispatchers. See Q614 for handler type hierarchy. See Q607 for sealed class requirements.","ek9Example":"defines module qa.dispatchervalidation.exhaustive\n\n  defines trait\n\n    //Sealed trait: requires exhaustive dispatch\n    Command allow only CreateCommand, UpdateCommand, DeleteCommand\n      execute() as abstract\n        <- rtn as String?\n\n  defines class\n\n    CreateCommand with trait of Command\n      override execute()\n        <- rtn as String: \"created\"\n\n    UpdateCommand with trait of Command\n      override execute()\n        <- rtn as String: \"updated\"\n\n    DeleteCommand with trait of Command\n      override execute()\n        <- rtn as String: \"deleted\"\n\n    //CORRECT: exhaustive dispatcher handles ALL permitted types\n    CommandProcessor\n      process() as dispatcher\n        -> cmd as Command\n        <- rtn as String: cmd.execute()\n\n      process()\n        -> cmd as CreateCommand\n        <- rtn as String: \"Processing create: \" + cmd.execute()\n\n      process()\n        -> cmd as UpdateCommand\n        <- rtn as String: \"Processing update: \" + cmd.execute()\n\n      process()\n        -> cmd as DeleteCommand\n        <- rtn as String: \"Processing delete: \" + cmd.execute()\n\n  defines function\n\n    testExhaustiveDispatch()\n      processor <- CommandProcessor()\n\n      createCmd <- CreateCommand()\n      updateCmd <- UpdateCommand()\n      deleteCmd <- DeleteCommand()\n\n      require processor.process(createCmd) == \"Processing create: created\"\n      require processor.process(updateCmd) == \"Processing update: updated\"\n      require processor.process(deleteCmd) == \"Processing delete: deleted\"","migrationContext":"Java: exhaustive switch on sealed types (Java 21+). Kotlin: exhaustive when on sealed classes/interfaces. Rust: exhaustive match on enums. Scala: exhaustive match on sealed traits. TypeScript: exhaustive switch with never type. EK9: E05260 enforces exhaustive dispatch handler coverage on sealed types.","keywords":["E05260","allow","ambiguity","closed","complete","coverage","dispatch","exhaustive","handler","only","sealed","validate","visitor"],"primaryTopics":[],"typicalErrors":[{"error":"E50060","correct":"require processor.process(createCmd) == \"Processing create: created\"","incorrect":"require processor.process(createCmd).toUpperCase() == \"Processing create: created\"","explanation":"String has no toUpperCase() method in EK9. Use upperCase() instead. See ek9 -h E50060 for details."},{"error":"E50001","correct":"process()\n        -> cmd as CreateCommand","incorrect":"process()\n        -> item as String","explanation":"The dispatcher entry takes Command. A handler for String is outside the Command hierarchy. All handler parameter types must be subtypes of the entry parameter type. See ek9 -h E50001 for details."},{"error":"E05260","correct":"      process()\n        -> cmd as CreateCommand\n        <- rtn as String: \"Processing create: \" + cmd.execute()\n\n      process()\n        -> cmd as UpdateCommand\n        <- rtn as String: \"Processing update: \" + cmd.execute()\n\n      process()\n        -> cmd as DeleteCommand\n        <- rtn as String: \"Processing delete: \" + cmd.execute()","incorrect":"      process()\n        -> cmd as CreateCommand\n        <- rtn as String: \"Processing create: \" + cmd.execute()\n\n      process()\n        -> cmd as UpdateCommand\n        <- rtn as String: \"Processing update: \" + cmd.execute()","explanation":"The sealed trait Command allows only CreateCommand, UpdateCommand, and DeleteCommand. Removing the DeleteCommand handler leaves the dispatcher non-exhaustive. All permitted types must have handlers. See ek9 -h E05260 for details."}],"companions":[]}
{"id":619,"category":"Dispatcher Validation","question":"What happens with two-parameter dispatchers and type validation?","url":"https://ek9.io/qa/QA0619.html","alternatePhrasings":["Can EK9 dispatchers dispatch on multiple parameters?","How does multi-parameter dispatch work in EK9?","Does E05210 apply to each parameter in a dispatcher?"],"answer":"EK9 supports true multiple dispatch: dispatchers can dispatch on two or more parameters simultaneously. The type hierarchy validation (E05210) applies to EACH dispatchable parameter independently.\n\nMULTIPLE DISPATCH\nA two-parameter dispatcher examines the runtime type of both arguments to select the most specific handler:\n  intersect() as dispatcher\n    -> s1 as Shape, s2 as Shape\n  intersect()\n    -> s1 as Circle, s2 as Circle\n  intersect()\n    -> s1 as Circle, s2 as Rectangle\n\nTYPE CHECKING PER PARAMETER\nEach handler parameter must be a subtype of the corresponding dispatcher entry parameter. If the first entry parameter is Shape, all handlers' first parameters must extend Shape. The same applies to the second parameter.\n\nCOST CALCULATION\nThe total dispatch cost is the sum of costs for each parameter. A handler with Circle (cost 0.05) + Rectangle (cost 0.05) has total cost 0.10. Exact matches on both parameters (cost 0.00 + 0.00) always win.\n\nAMBIGUITY WITH MULTIPLE PARAMETERS\nAmbiguity (E05230) can occur when two handlers have the same total cost. For example, handler(Circle, Rectangle) and handler(Rectangle, Circle) both cost 0.10 when called with a type that matches both.\n\nTRUE MULTIPLE DISPATCH\nThis is true multiple dispatch, a feature only Julia and Common Lisp natively support among mainstream languages. EK9 provides it with compile-time validation.\n\nSee Q60 for dispatcher fundamentals. See Q614 for single-parameter hierarchy. See Q616 for ambiguity detection.","ek9Example":"defines module qa.dispatchervalidation.twoparam\n\n  defines class\n\n    Shape as abstract\n      name() as pure abstract\n        <- rtn as String?\n      default operator ?\n\n    Circle extends Shape\n      override name() as pure\n        <- rtn as String: \"circle\"\n      default operator ?\n\n    Rectangle extends Shape\n      override name() as pure\n        <- rtn as String: \"rectangle\"\n      default operator ?\n\n    //CORRECT: Two-parameter dispatcher with all types in hierarchy\n    CollisionDetector\n      collides() as dispatcher\n        ->\n          s1 as Shape\n          s2 as Shape\n        <- rtn as String: `${s1.name()} vs ${s2.name()}`\n\n      collides()\n        ->\n          s1 as Circle\n          s2 as Circle\n        <- rtn as String: \"circle-circle collision\"\n\n      collides()\n        ->\n          s1 as Circle\n          s2 as Rectangle\n        <- rtn as String: \"circle-rectangle collision\"\n\n      collides()\n        ->\n          s1 as Rectangle\n          s2 as Rectangle\n        <- rtn as String: \"rectangle-rectangle collision\"\n\n  defines function\n\n    testTwoParamDispatch()\n      detector <- CollisionDetector()\n\n      circle <- Circle()\n      rect <- Rectangle()\n\n      require detector.collides(circle, circle) == \"circle-circle collision\"\n      require detector.collides(circle, rect) == \"circle-rectangle collision\"\n      require detector.collides(rect, rect) == \"rectangle-rectangle collision\"","migrationContext":"Java: no multiple dispatch (single dispatch via virtual methods). Python: functools.singledispatch (single parameter only). Julia: built-in multiple dispatch (most similar to EK9). Common Lisp: CLOS multi-methods. Rust: no dispatch mechanism. Kotlin: no multiple dispatch. EK9: true multiple dispatch with compile-time E05210 validation per parameter.","keywords":["E05210","E05230","ambiguity","cost","dispatch","handler","multi","multiple","parameter","sealed","true","two","validate","visitor"],"primaryTopics":[],"typicalErrors":[{"error":"E05210","correct":"collides()\n        ->\n          s1 as Circle\n          s2 as Rectangle","incorrect":"collides()\n        ->\n          s1 as Circle\n          s2 as String","explanation":"Each parameter in a multi-parameter dispatcher must be a subtype of the corresponding entry parameter. The entry takes Shape for both, so String is invalid for either parameter. See ek9 -h E05210 for details."},{"error":"E05220","correct":"collides()\n        ->\n          s1 as Circle\n          s2 as Circle\n        <- rtn as String: \"circle-circle collision\"","incorrect":"collides()\n        ->\n          s1 as Circle\n          s2 as Circle\n        <- rtn as Integer: 0","explanation":"All dispatcher handlers must return the same type as the entry method. The entry returns String, so a handler returning Integer is incompatible. See ek9 -h E05220 for details."}],"companions":[]}
{"id":620,"category":"Dispatcher Validation","question":"How does dispatcher resolution work across a class hierarchy?","url":"https://ek9.io/qa/QA0620.html","alternatePhrasings":["What are the dispatch resolution rules in EK9?","How does EK9 choose which handler to call?","What is the cost model for dispatcher resolution?"],"answer":"EK9 uses a cost-based model to resolve dispatchers. When a dispatcher is called, the compiler has already validated the hierarchy at compile time. At runtime, the most specific (lowest cost) handler is selected.\n\nCOST MODEL\nExact type match: cost 0.00. Each class inheritance step: cost 0.05 per level. Each trait implementation step: cost 0.10 per level. The handler with the lowest total cost wins.\n\nFALLBACK TO ENTRY\nIf no specific handler matches, the dispatcher entry method executes. This is the catch-all fallback for types without dedicated handlers.\n\nINHERITANCE DEPTH MATTERS\nA handler for a direct parent (cost 0.05) beats a handler for a grandparent (cost 0.10). More specific handlers always win over less specific ones.\n\nCOMPILE-TIME VALIDATION\nAll type hierarchy checks (E05210), return type checks (E05220), purity checks (E05170), and access checks (E05180) happen at compile time. Runtime dispatch only selects the handler; correctness is guaranteed by the compiler.\n\nTRAIT VS CLASS COST\nTrait implementation costs more than class inheritance (0.10 vs 0.05). This reflects the structural reality that class inheritance is a stronger type relationship than trait implementation.\n\nSee Q60 for dispatcher fundamentals. See Q612 for purity matching. See Q614 for type hierarchy validation. See Q255 for cost-based method resolution.","ek9Example":"defines module qa.dispatchervalidation.rules\n\n  defines class\n\n    //Three-level hierarchy to demonstrate cost-based resolution\n    Entity as abstract\n      kind() as pure abstract\n        <- rtn as String?\n      default operator ?\n\n    LivingEntity extends Entity as open\n      override kind() as pure\n        <- rtn as String: \"living\"\n      default operator ?\n\n    Person extends LivingEntity\n      override kind() as pure\n        <- rtn as String: \"person\"\n      default operator ?\n\n    //Dispatcher with handlers at different hierarchy depths\n    EntityProcessor\n      process() as dispatcher\n        -> entity as Entity\n        <- rtn as String: \"Generic: \" + entity.kind()\n\n      //Cost 0.05 from LivingEntity (one step from Entity)\n      process()\n        -> entity as LivingEntity\n        <- rtn as String: \"Living: \" + entity.kind()\n\n      //Cost 0.10 from Person (two steps from Entity)\n      //But cost 0.00 for exact Person match\n      process()\n        -> entity as Person\n        <- rtn as String: \"Person: \" + entity.kind()\n\n  defines function\n\n    testCostBasedResolution()\n      processor <- EntityProcessor()\n\n      person <- Person()\n      //Person matches Person handler at cost 0.00 (exact match)\n      require processor.process(person) == \"Person: person\"","migrationContext":"Java: virtual dispatch via vtable (single dispatch, O(1)). C++: virtual function tables with dynamic_cast for type checking. Python: MRO-based method resolution. Kotlin: no runtime dispatch, compile-time overload resolution. Julia: multiple dispatch with method cache. EK9: cost-based multiple dispatch with compile-time hierarchy validation.","keywords":["ambiguity","class","compile","cost","depth","dispatch","entry","fallback","handler","hierarchy","resolution","sealed","validate","visitor"],"primaryTopics":[],"typicalErrors":[{"error":"E50060","correct":"process()\n        -> entity as Person","incorrect":"process()\n        -> entity as String","explanation":"The dispatcher entry takes Entity. All handler parameter types must be within the Entity hierarchy. String is unrelated and would never match. See ek9 -h E50060 for details."},{"error":"E50060","correct":"require processor.process(person) == \"Person: person\"","incorrect":"require processor.process(person).toUpperCase() == \"Person: person\"","explanation":"String has no toUpperCase() method in EK9. Use upperCase() instead. See ek9 -h E50060 for details."}],"companions":[]}
{"id":621,"category":"Dispatcher Validation","question":"What is the complete set of dispatcher compile-time checks?","url":"https://ek9.io/qa/QA0621.html","alternatePhrasings":["What dispatcher errors can EK9 detect?","How does EK9 validate dispatchers at compile time?","What are all the dispatcher-related E05xxx error codes?"],"answer":"EK9 performs six distinct compile-time checks on dispatchers, catching errors that would be runtime failures in other languages.\n\nPURITY MATCHING (E05170)\nAll handlers must match the dispatcher entry's purity. A pure dispatcher requires all handlers to be pure. A non-pure dispatcher requires all handlers to be non-pure.\n\nACCESS VALIDATION (E05180)\nDispatcher handlers must be accessible. A dispatcher in a child class cannot target a private method in the parent class.\n\nTYPE HIERARCHY (E05210)\nEvery handler's parameter type must be a subtype of the entry's parameter type. Handlers for unrelated types are rejected.\n\nRETURN TYPE (E05220)\nAll handlers must return the same type (or a covariant subtype) as the entry method.\n\nAMBIGUITY DETECTION (E05230)\nWhen two handlers match at equal cost (especially with multiple trait implementations), the compiler rejects the ambiguity rather than guessing.\n\nEXHAUSTIVE COVERAGE (E05260)\nDispatchers on sealed types (with 'allow only') must have handlers for all permitted concrete types.\n\nTOGETHER\nThese six checks guarantee that at runtime, every dispatch call will find exactly one valid handler with correct purity, access, type, and return type. No runtime dispatch errors are possible.\n\nSee Q612 for E05170. See Q613 for E05180. See Q614 for E05210. See Q615 for E05220. See Q616 for E05230. See Q618 for E05260.","ek9Example":"defines module qa.dispatchervalidation.summary\n\n  defines trait\n\n    //Sealed trait for exhaustive dispatch demo\n    Action allow only SaveAction, LoadAction\n      perform() as pure abstract\n        <- rtn as String?\n\n  defines class\n\n    SaveAction with trait of Action\n      override perform() as pure\n        <- rtn as String: \"saved\"\n\n    LoadAction with trait of Action\n      override perform() as pure\n        <- rtn as String: \"loaded\"\n\n    //Dispatcher demonstrating multiple checks passing\n    ActionRunner\n      //Pure dispatcher (E05170 check: all handlers pure)\n      run() as pure dispatcher\n        -> action as Action\n        <- rtn as String: action.perform()\n\n      //Handler type in hierarchy (E05210 check)\n      //Return type matches (E05220 check)\n      //Exhaustive coverage (E05260 check: both SaveAction and LoadAction handled)\n      run() as pure\n        -> action as SaveAction\n        <- rtn as String: \"Running save: \" + action.perform()\n\n      run() as pure\n        -> action as LoadAction\n        <- rtn as String: \"Running load: \" + action.perform()\n\n  defines function\n\n    testDispatcherChecks()\n      runner <- ActionRunner()\n\n      saveAction <- SaveAction()\n      loadAction <- LoadAction()\n\n      require runner.run(saveAction) == \"Running save: saved\"\n      require runner.run(loadAction) == \"Running load: loaded\"","migrationContext":"Java: only static overload resolution, no runtime dispatch validation. Kotlin: exhaustive when on sealed types only. Rust: exhaustive match on enums but no multi-dispatch. Julia: multiple dispatch with runtime ambiguity errors. Python: singledispatch with no compile-time validation. EK9: six compile-time dispatch checks eliminate all runtime dispatch errors.","keywords":["E05170","E05180","E05210","E05220","E05230","E05260","ambiguity","compile","dispatch","dispatcher","summary","validate","validation"],"primaryTopics":[],"typicalErrors":[{"error":"E05170","correct":"run() as pure\n        -> action as SaveAction","incorrect":"run()\n        -> action as SaveAction","explanation":"The dispatcher entry run is declared as pure. Every handler must match this purity level. Omitting as pure on a handler creates a purity mismatch. See ek9 -h E05170 for details."},{"error":"E50060","correct":"require runner.run(saveAction) == \"Running save: saved\"","incorrect":"require runner.run(saveAction).toUpperCase() == \"Running save: saved\"","explanation":"String has no toUpperCase() method in EK9. Use upperCase() instead. See ek9 -h E50060 for details."},{"error":"E50001","correct":"run() as pure\n        -> action as SaveAction","incorrect":"run() as pure\n        -> item as String","explanation":"The dispatcher entry takes Action. All handler parameter types must be subtypes of Action. String is outside the Action hierarchy. See ek9 -h E50001 for details."}],"companions":[]}
{"id":622,"category":"Code Formatting","question":"How do I format an EK9 source file?","url":"https://ek9.io/qa/QA0622.html","alternatePhrasings":["How do I auto-format my EK9 code?","Does EK9 have a built-in formatter like gofmt?","How do I canonically format a single .ek9 file?"],"answer":"EK9 has a built-in code formatter, similar to gofmt for Go or rustfmt for Rust. Use the -f flag to format a single file.\n\nBASIC USAGE\n  ek9 -f myfile.ek9\nThis parses the file first, then rewrites it in canonical format. The file must be valid EK9 (parseable) for -f to work.\n\nWHAT IT CHANGES\n- Normalises indentation to canonical two-space indent.\n- Standardises whitespace around operators and keywords.\n- Aligns block structure consistently.\n- Removes trailing whitespace.\n- Ensures consistent blank-line usage between constructs.\n\nWHAT IT PRESERVES\n- Comments (both doc-comments and inline comments) are preserved.\n- Blank lines that separate logical sections are maintained.\n- The semantic meaning of the code is never altered.\n\nWHEN TO USE -f\nUse -f after writing or editing a file manually. It is safe to run repeatedly because the output is idempotent: formatting an already-formatted file produces the same file.\n\nSee Q623 for formatting all project files. See Q624 for force-formatting unparseable files. See Q625 for why EK9 enforces a standard format. See Q626 for integrating formatting into your workflow. See Q16 for indentation rules.","ek9Example":"defines module qa.codeformatting.single\n\n  defines function\n\n    <?-\n      A function that would be reformatted by ek9 -f\n      if written with inconsistent spacing.\n      After formatting, indentation is canonical.\n    -?>\n    greet() as pure\n      -> name as String\n      <- message as String: `Hello, ${name}!`\n\n    calculateArea() as pure\n      ->\n        width as Float\n        height as Float\n      <-\n        area as Float: width * height\n\n  defines program\n\n    FormatDemo()\n      stdout <- Stdout()\n\n      greeting <- greet(\"World\")\n      stdout.println(greeting)\n\n      roomArea <- calculateArea(4.5, 3.2)\n      stdout.println(`Area: ${roomArea}`)","migrationContext":"Go: gofmt built into toolchain, universally adopted, single canonical style. Rust: rustfmt (cargo fmt) built into toolchain. Python: black (external, opinionated), autopep8, yapf. Java: google-java-format, spotless (all external plugins). JavaScript: prettier (external). EK9: ek9 -f built into compiler binary, same philosophy as gofmt (one true format, no configuration).","keywords":["auto-format","canonical","format","formatter","gofmt","indent","migrate","rustfmt","style","whitespace"],"primaryTopics":["format code","code formatting","auto format"],"typicalErrors":[{"error":"E50001","correct":"greeting <- greet(\"World\")","incorrect":"greet(\"World\")","explanation":"Removing the variable declaration means later references to the variable become unresolved, triggering E50001. See ek9 -h E50001 for details."}],"companions":[]}
{"id":623,"category":"Code Formatting","question":"How do I format all EK9 source files in my project?","url":"https://ek9.io/qa/QA0623.html","alternatePhrasings":["How do I format an entire EK9 project at once?","What does ek9 -F do?","How do I bulk-format all .ek9 files?"],"answer":"Use the -F flag (uppercase) to format all EK9 source files in the project at once.\n\nBASIC USAGE\n  ek9 -F myproject.ek9\nThis discovers all .ek9 files referenced by the project and formats each one to canonical style. Every file must be parseable for this to succeed.\n\nDIFFERENCE FROM -f\n- ek9 -f myfile.ek9 formats a single named file.\n- ek9 -F myproject.ek9 formats all files in the project.\nBoth require the files to parse successfully first.\n\nTYPICAL WORKFLOW\nAfter cloning a project or pulling changes:\n  ek9 -F myproject.ek9\nThis ensures your entire codebase follows the canonical format before you start editing.\n\nAfter a code review or merge:\n  ek9 -F myproject.ek9\nThis normalises any formatting differences introduced during the merge.\n\nIDEMPOTENCY\nRunning -F on an already-formatted project produces no changes. This makes it safe to run in CI to verify that all files are correctly formatted.\n\nSee Q622 for formatting a single file. See Q624 for force-formatting unparseable files. See Q625 for why EK9 enforces a standard format. See Q626 for integrating formatting into your workflow.","ek9Example":"defines module qa.codeformatting.allfiles\n\n  defines function\n\n    <?-\n      Multiple functions representing a multi-file project.\n      After ek9 -F, every file follows the same format.\n    -?>\n    add() as pure\n      ->\n        left as Integer\n        right as Integer\n      <-\n        result as Integer: left + right\n\n    multiply() as pure\n      ->\n        left as Integer\n        right as Integer\n      <-\n        result as Integer: left * right\n\n  defines program\n\n    FormatAllDemo()\n      stdout <- Stdout()\n\n      sum <- add(3, 4)\n      product <- multiply(3, 4)\n\n      stdout.println(`Sum: ${sum}`)\n      stdout.println(`Product: ${product}`)","migrationContext":"Go: gofmt ./... formats all Go files in directory tree. Rust: cargo fmt formats all crate sources. Python: black . formats all Python files in directory. Java: google-java-format with glob patterns (manual setup). JavaScript: prettier --write . (external tool). EK9: ek9 -F formats all project sources in one command, built into the compiler.","keywords":["F","all","bulk","canonical","files","format","indent","normalise","project","style"],"primaryTopics":[],"typicalErrors":[{"error":"E50001","correct":"sum <- add(3, 4)","incorrect":"add(3, 4)","explanation":"Removing the variable declaration means later references to the variable become unresolved, triggering E50001. See ek9 -h E50001 for details."},{"error":"E50060","correct":"stdout.println(`Product: ${product}`)","incorrect":"stdout.println(product.toString())","explanation":"Integer has no toString() method; convert with the $ prefix operator ($product) or use string interpolation. See ek9 -h E50060 for details."}],"companions":[]}
{"id":624,"category":"Code Formatting","question":"How do I format an EK9 file that does not parse?","url":"https://ek9.io/qa/QA0624.html","alternatePhrasings":["What does ek9 -ff do?","How do I force-format a broken EK9 file?","Can the formatter fix syntax errors in my code?"],"answer":"Use the -ff flag (force format) to attempt formatting on files that cannot be parsed. Use -fF to force-format all project files.\n\nFORCE FORMAT SINGLE FILE\n  ek9 -ff myfile.ek9\nThis attempts to reformat the file even if it contains syntax errors. The formatter applies what corrections it can based on the partial parse.\n\nFORCE FORMAT ALL FILES\n  ek9 -fF myproject.ek9\nThis force-formats every .ek9 file in the project, attempting to fix unparseable files.\n\nALL FOUR FORMAT FLAGS\n  ek9 -f  myfile.ek9    Format single file (must parse).\n  ek9 -F  myproject.ek9  Format all project files (must parse).\n  ek9 -ff myfile.ek9    Force format single file (attempts fix).\n  ek9 -fF myproject.ek9  Force format all project files (attempts fix).\n\nWHEN TO USE FORCE FORMAT\n- After pasting code from another language that needs significant restructuring.\n- When a merge conflict leaves a file in a broken state.\n- When indentation has become corrupted (tabs mixed with spaces, wrong levels).\n- When AI-generated code has formatting issues that prevent parsing.\n\nLIMITATIONS\nForce format makes a best-effort attempt. If the file is severely broken (missing keywords, fundamentally wrong structure), the formatter cannot fully recover it. Always compile after force-formatting to verify the result.\n\nSee Q622 for standard single-file formatting. See Q623 for formatting all project files. See Q625 for why EK9 enforces a standard format.","ek9Example":"defines module qa.codeformatting.force\n\n  defines function\n\n    <?-\n      A well-formatted function.\n      After force-formatting a broken version, it would look like this.\n    -?>\n    sanitizeInput() as pure\n      -> raw as String\n      <- cleaned as String: raw.trim()\n\n    isNonEmpty() as pure\n      -> text as String\n      <- result as Boolean: text?\n\n  defines program\n\n    ForceFormatDemo()\n      stdout <- Stdout()\n\n      userInput <- \"  hello world  \"\n      cleaned <- sanitizeInput(userInput)\n      hasContent <- isNonEmpty(cleaned)\n\n      stdout.println(`Cleaned: '${cleaned}'`)\n      stdout.println(`Has content: ${hasContent}`)","migrationContext":"Go: gofmt requires valid Go syntax, no force mode. Rust: rustfmt requires valid Rust syntax, no force mode. Python: black requires valid Python syntax. JavaScript: prettier can handle some malformed code. EK9: ek9 -ff provides explicit force-format mode for broken files, a unique capability among language formatters.","keywords":["broken","error","fF","ff","fix","force","format","indent","recover","style","syntax","unparseable"],"primaryTopics":[],"typicalErrors":[{"error":"E50001","correct":"cleaned <- sanitizeInput(userInput)","incorrect":"sanitizeInput(userInput)","explanation":"Removing the variable declaration means later references to the variable become unresolved, triggering E50001. See ek9 -h E50001 for details."}],"companions":[]}
{"id":625,"category":"Code Formatting","question":"Why does EK9 enforce a single standard format?","url":"https://ek9.io/qa/QA0625.html","alternatePhrasings":["Why can I not configure the EK9 formatter?","Why does EK9 have only one formatting style?","What is the benefit of a non-configurable formatter?"],"answer":"EK9 enforces a single canonical format for the same reason Go does with gofmt: formatting debates are a waste of engineering time.\n\nTHE GOFMT PRINCIPLE\nGo's gofmt proved that a single, non-configurable formatter eliminates an entire category of team friction. Before gofmt, Go teams argued about brace placement, tab width, and blank lines. After gofmt, every Go file looks the same. The debate simply vanished.\n\nEK9 APPLIES THE SAME LOGIC\nThe EK9 formatter produces one canonical output. There are no configuration flags for indent size, brace style, or line length. This is a deliberate design decision, not a missing feature.\n\nBENEFITS\n1. Zero formatting debates in code reviews. Every file looks identical.\n2. Diffs show only semantic changes, never whitespace noise.\n3. AI-generated code matches human-written code exactly.\n4. New team members produce correctly-formatted code from day one.\n5. Merge conflicts from formatting differences are eliminated.\n\nTHIS IS PART OF THE VERTICAL INTEGRATION\nEK9 collapses 15+ external tools into the compiler. The formatter is one of them. Instead of configuring prettier, ESLint, editorconfig, and pre-commit hooks separately, EK9 provides ek9 -f built into the same binary that compiles, tests, and packages your code.\n\nSee Q622 for formatting a single file. See Q623 for formatting all project files. See Q310 for quality checks at compile time. See Q16 for indentation rules.","ek9Example":"defines module qa.codeformatting.whystandard\n\n  defines class\n\n    <?-\n      A class written in canonical EK9 format.\n      Every developer's version of this class looks identical\n      after formatting because there is only one style.\n    -?>\n    Temperature\n      degrees as Float: 0.0\n      scale as String: \"C\"\n\n      Temperature()\n        ->\n          initialValue as Float\n          initialScale as String\n        degrees: initialValue\n        scale: initialScale\n\n      toCelsius() as pure\n        <- result as Float: degrees\n\n      toDisplay() as pure\n        <- result as String: `${degrees} ${scale}`\n\n      operator $ as pure\n        <- result as String: toDisplay()\n\n      default operator ?\n\n  defines program\n\n    WhyStandardFormatDemo()\n      stdout <- Stdout()\n\n      boiling <- Temperature(100.0, \"C\")\n      stdout.println($boiling)","migrationContext":"Go: gofmt established the 'one true format' principle. No configuration, universally adopted. Rust: rustfmt has some configuration options but defaults are strongly encouraged. Python: black is 'the uncompromising formatter' (few options). Java: teams still argue about google-java-format vs checkstyle vs IntelliJ defaults. JavaScript: prettier has some configuration. EK9: follows gofmt philosophy exactly, zero configuration, one canonical format.","keywords":["canonical","configure","convention","debate","format","gofmt","indent","migrate","opinionated","standard","style","team"],"primaryTopics":[],"typicalErrors":[{"error":"E50060","correct":"stdout.println($boiling)","incorrect":"stdout.println(boiling.toString())","explanation":"Temperature has no toString() method. Use the $ prefix operator for string conversion. See ek9 -h E50060 for details."},{"error":"E50060","correct":"boiling <- Temperature(100.0, \"C\")","incorrect":"boiling <- Temperature(100.0, \"C\").toCelsius().intValue()","explanation":"Float has no intValue() method in EK9. Use explicit conversion if needed. See ek9 -h E50060 for details."}],"companions":[]}
{"id":626,"category":"Code Formatting","question":"How do I integrate EK9 formatting into my development workflow?","url":"https://ek9.io/qa/QA0626.html","alternatePhrasings":["Should I format before or after committing?","How do I use the EK9 formatter in CI?","How do I set up a format-on-save workflow for EK9?"],"answer":"The EK9 formatter integrates naturally into both local development and CI pipelines.\n\nLOCAL WORKFLOW\nFormat before committing:\n  ek9 -F myproject.ek9\n  git add -A\n  git commit -m \"feature: add new module\"\nThis ensures every commit contains canonically formatted code.\n\nPRE-COMMIT HOOK\nAdd a git pre-commit hook that formats and re-stages:\n  #!/bin/sh\n  ek9 -F myproject.ek9\n  git add -u\nThis catches formatting issues before they reach the repository.\n\nCI VERIFICATION\nIn CI, verify formatting without modifying files. Run:\n  ek9 -F myproject.ek9\n  git diff --exit-code\nIf -F produces any changes, git diff returns non-zero and the CI step fails. This ensures all committed code is correctly formatted.\n\nAFTER AI CODE GENERATION\nWhen an AI generates EK9 code, format it immediately:\n  ek9 -ff generated.ek9\nThe -ff (force format) handles cases where the AI output might not parse perfectly. Then compile to verify correctness.\n\nAFTER MERGE\nAfter resolving merge conflicts:\n  ek9 -fF myproject.ek9\nForce-format handles any indentation corruption from the merge.\n\nSee Q622 for formatting a single file. See Q623 for formatting all files. See Q624 for force-formatting. See Q625 for why EK9 enforces a standard format. See Q281 for AI code verification.","ek9Example":"defines module qa.codeformatting.workflow\n\n  defines function\n\n    <?-\n      Functions that represent a typical workflow:\n      write code, format, compile, test, commit.\n    -?>\n    validateAge() as pure\n      -> age as Integer\n      <- valid as Boolean: false\n\n      minimumAge <- 0\n      maximumAge <- 150\n      if age >= minimumAge and age <= maximumAge\n        valid: true\n\n    formatName() as pure\n      ->\n        firstName as String\n        lastName as String\n      <-\n        fullName as String: `${firstName} ${lastName}`\n\n  defines program\n\n    WorkflowDemo()\n      stdout <- Stdout()\n\n      ageIsValid <- validateAge(25)\n      name <- formatName(\"Jane\", \"Doe\")\n\n      stdout.println(`Name: ${name}`)\n      stdout.println(`Valid age: ${ageIsValid}`)","migrationContext":"Go: gofmt integrated into most editors, goimports for import management, CI uses gofmt -l to check. Rust: cargo fmt, CI uses cargo fmt --check. Python: black with pre-commit framework, CI uses black --check. Java: spotless plugin in Maven/Gradle, CI runs spotless:check. JavaScript: prettier with husky/lint-staged, CI runs prettier --check. EK9: ek9 -F for formatting, git diff --exit-code in CI, -ff for AI/merge recovery, all built into one binary.","keywords":["CI","commit","format","git","hook","indent","integrate","pipeline","pre-commit","save","style","verify","workflow"],"primaryTopics":[],"typicalErrors":[{"error":"E50001","correct":"ageIsValid <- validateAge(25)","incorrect":"validateAge(25)","explanation":"Removing the variable declaration means later references to the variable become unresolved, triggering E50001. See ek9 -h E50001 for details."}],"companions":[]}
{"id":627,"category":"Profiling","question":"How do I profile an EK9 program?","url":"https://ek9.io/qa/QA0627.html","alternatePhrasings":["How do I find performance bottlenecks in EK9?","What is the ek9 -tp flag for?","How do I measure function call times in EK9?"],"answer":"EK9 has built-in profiling. Append 'p' to any test flag to enable performance profiling.\n\nBASIC PROFILING\n  ek9 -tp myproject.ek9\nThis runs all tests with profiling instrumentation and produces a human-readable summary showing call counts and timing for each function and method.\n\nPROFILING OUTPUT FORMATS\nThe 'p' suffix works with every test format:\n  ek9 -tp  myproject.ek9   Human-readable profiling summary.\n  ek9 -t0p myproject.ek9   Terse profiling (CI pass/fail).\n  ek9 -t2p myproject.ek9   JSON profiling data (for AI and tools).\n  ek9 -t3p myproject.ek9   JUnit XML with profiling annotations.\n  ek9 -t6p myproject.ek9   HTML dashboard with flame graph.\n\nWHAT GETS MEASURED\nFor each function and method the profiler records:\n- Call count: how many times it was invoked.\n- Total time: wall-clock time including calls to other functions.\n- Self time: time in this function only, excluding callees.\n- Average, minimum, and maximum call times.\n- Percentile distribution: p50, p95, p99.\n\nNO OPTIMISATION\nProfiling automatically forces -O0 (no optimisation). This ensures that probe-to-source mapping is accurate. Every function call is measured as written, with no inlining or dead code elimination.\n\nSee Q628 for profiling tests specifically. See Q629 for reading flame graphs. See Q630 for identifying hot methods. See Q631 for benchmarking two approaches. See Q322 for profiling overview. See Q207 for test output formats. See Q752 for fuzz HTML dashboard (similar report pattern).","ek9Example":"defines module qa.profilingdeep.program\n\n  defines function\n\n    <?-\n      Recursive function that shows up as a hot method in profiling.\n      The profiler records call count and self-time for each invocation.\n    -?>\n    factorial() as pure\n      -> number as Integer\n      <- result as Integer: 1\n\n      recursionBase <- 1\n      if number > recursionBase\n        subResult <- factorial(number - 1)\n        result: number * subResult\n\n    sumUpTo() as pure\n      -> limit as Integer\n      <- total as Integer: 0\n\n      for i in 1 ... limit\n        total: total + i\n\n  defines program\n\n    ProfileProgramDemo()\n      stdout <- Stdout()\n\n      tenFactorial <- 10\n      result <- factorial(tenFactorial)\n      stdout.println(`10! = ${result}`)\n\n      hundredSum <- 100\n      sum <- sumUpTo(hundredSum)\n      stdout.println(`Sum 1..100 = ${sum}`)","migrationContext":"Java: async-profiler, JFR, or VisualVM (all external tools, require JVM flags). Python: cProfile (stdlib), py-spy (external, sampling). Rust: perf, flamegraph crate (external). Go: go tool pprof with -cpuprofile flag (built-in but requires code changes). JavaScript: Chrome DevTools profiler (browser only). EK9: append 'p' to any test flag for built-in profiling, zero code changes, multiple output formats including JSON and HTML flame graph.","keywords":["O0","bottleneck","call","count","flame-graph","performance","profile","profiling","program","self","time","total","tp"],"primaryTopics":[],"typicalErrors":[{"error":"E50001","correct":"result <- factorial(tenFactorial)","incorrect":"factorial(tenFactorial)","explanation":"Removing the variable declaration means later references to the variable become unresolved, triggering E50001. See ek9 -h E50001 for details."}],"companions":[]}
{"id":628,"category":"Profiling","question":"How do I profile my EK9 tests?","url":"https://ek9.io/qa/QA0628.html","alternatePhrasings":["How do I find slow tests in EK9?","What does ek9 -t2p output look like?","How do I get JSON profiling data from EK9 tests?"],"answer":"Every test format supports profiling by appending 'p'. The profiler instruments all code paths exercised by your tests.\n\nPROFILE ALL TESTS\n  ek9 -tp myproject.ek9\nRuns all @Test functions with profiling. The human-readable output lists each function with call count and timing, sorted by self-time.\n\nJSON OUTPUT FOR ANALYSIS\n  ek9 -t2p myproject.ek9\nProduces structured JSON that includes profiling data per function. This is ideal for:\n- AI assistants analysing performance.\n- CI pipelines checking latency thresholds.\n- Trend analysis tools tracking performance over builds.\n\nHTML DASHBOARD\n  ek9 -t6p myproject.ek9\nGenerates an interactive HTML page with:\n- Flame graph visualisation.\n- Hot function table sorted by self-time.\n- Call tree with timing annotations.\n- Source views with timing badges per function.\n\nFINDING SLOW TESTS\nThe profiler measures all code called during test execution. If a test is slow, the profiler shows which function under that test consumed the most time. This distinguishes between:\n- Slow test setup (the test infrastructure).\n- Slow application code (the code under test).\n- Excessive assertions (too many checks per test).\n\nFILTERING\nProfile specific test groups:\n  ek9 -tp -tg performance myproject.ek9\nThis profiles only tests in the 'performance' group.\n\nSee Q627 for profiling programs. See Q629 for reading flame graphs. See Q630 for identifying hot methods. See Q155 for writing unit tests. See Q157 for running tests. See Q207 for test output formats.","ek9Example":"defines module qa.profilingdeep.tests\n\n  defines function\n\n    <?-\n      Functions that tests would exercise.\n      Profiling reveals which functions consume the most time.\n    -?>\n    isPrime()\n      -> candidate as Integer\n      <- prime as Boolean: false\n\n      minimumPrime <- 2\n      if candidate >= minimumPrime\n        prime: true\n        divisor <- 2\n        squaredDivisor <- divisor * divisor\n        while squaredDivisor <= candidate and prime\n          if candidate mod divisor == 0\n            prime: false\n          divisor: divisor + 1\n          squaredDivisor: divisor * divisor\n\n    countPrimesBelow()\n      -> limit as Integer\n      <- count as Integer: 0\n\n      for candidate in 2 ... limit\n        if isPrime(candidate)\n          count: count + 1\n\n  defines program\n\n    ProfileTestsDemo()\n      stdout <- Stdout()\n\n      hundredLimit <- 100\n      primeCount <- countPrimesBelow(hundredLimit)\n      stdout.println(`Primes below 100: ${primeCount}`)","migrationContext":"Java: JUnit + JMH for microbenchmarks (separate framework), async-profiler for test profiling (external tool). Python: pytest-benchmark (plugin), cProfile with test runner (manual setup). Rust: criterion for benchmarks (external crate), no built-in test profiling. Go: go test -bench for benchmarks, -cpuprofile for profiling (separate flags). EK9: append 'p' to any test flag, profiling is built into the test runner, no separate framework needed.","keywords":["HTML","JSON","benchmark","dashboard","flame","flame-graph","group","performance","profile","slow","t2p","t6p","test"],"primaryTopics":[],"typicalErrors":[{"error":"E50060","correct":"      stdout.println(`Primes below 100: ${primeCount}`)","incorrect":"      stdout.println(primeCount.toString())","explanation":"Integer has no toString() method in EK9; `primeCount.toString()` triggers E50060 - use the $ prefix operator or `${primeCount}` interpolation. See ek9 -h E50060 for details."},{"error":"E50060","correct":"primeCount <- countPrimesBelow(hundredLimit)","incorrect":"primeCount <- countPrimesBelow(hundredLimit).intValue()","explanation":"Integer has no intValue() method in EK9. Integer values are used directly. See ek9 -h E50060 for details."}],"companions":[]}
{"id":629,"category":"Profiling","question":"How do I read an EK9 flame graph?","url":"https://ek9.io/qa/QA0629.html","alternatePhrasings":["What do the colours in the flame graph mean?","How do I interpret the EK9 profiling dashboard?","What is a flame graph and how do I use it?"],"answer":"The -t6p flag generates an interactive HTML flame graph. Understanding how to read it is the key skill for performance work.\n\nGENERATING A FLAME GRAPH\n  ek9 -t6p myproject.ek9\nOutput: .ek9/coverage/index.html (open in browser).\n\nREADING THE GRAPH\nEach horizontal bar represents a function. The bars are stacked vertically to show the call chain: the bottom bar is the entry point, and each bar above it is a function called by the one below.\n\nWIDTH = TIME\nThe width of each bar represents total time spent in that function (including its callees). A wide bar means that call path consumed a large fraction of execution time.\n\nCOLOUR MEANING\n- Red frames: high self-time. These functions are doing the actual work. They are your primary optimisation targets.\n- Blue frames: orchestrators. High total time but low self-time. They spend time calling other functions, not doing work themselves.\n- Narrow frames: fast or rarely called. Usually not worth optimising.\n\nINTERACTIVE FEATURES\n- Click a frame to zoom into that subtree.\n- Hover to see exact call count, self-time, and total time.\n- Search to highlight all frames matching a function name.\n- Reset to return to the full view.\n\nACTIONABLE STRATEGY\n1. Look for the widest red frames. These are your bottlenecks.\n2. Check call count. A fast function called millions of times can dominate.\n3. Compare self-time vs total time. If total is high but self is low, the bottleneck is deeper in the call chain.\n4. Click to zoom and find the deepest hot function.\n\nSee Q627 for profiling programs. See Q630 for identifying hot methods. See Q321 for the quality report dashboard. See Q322 for profiling overview.","ek9Example":"defines module qa.profilingdeep.flamegraph\n\n  defines function\n\n    <?-\n      A call chain that produces a clear flame graph:\n      orchestrate calls processItems which calls transformItem.\n      In the flame graph, orchestrate is at the bottom (wide),\n      processItems in the middle, and transformItem at the top.\n    -?>\n    transformItem() as pure\n      -> item as Integer\n      <- result as Integer: item * item + 1\n\n    processItems()\n      -> items as List of Integer\n      <- total as Integer: 0\n\n      for item in items\n        transformed <- transformItem(item)\n        total: total + transformed\n\n    orchestrate()\n      -> size as Integer\n      <- result as Integer: 0\n\n      items <- List() of Integer\n      for i in 1 ... size\n        items += i\n      result: processItems(items)\n\n  defines program\n\n    FlameGraphDemo()\n      stdout <- Stdout()\n\n      batchSize <- 50\n      result <- orchestrate(batchSize)\n      stdout.println(`Result: ${result}`)","migrationContext":"Java: async-profiler generates flame graphs (SVG), JFR with JMC for interactive analysis. Python: py-spy generates flame graphs (SVG/HTML). Rust: flamegraph crate wraps perf into SVG output. Go: go tool pprof generates flame graphs. JavaScript: Chrome DevTools flame chart. EK9: built-in HTML flame graph from -t6p, interactive with zoom/search/hover, integrated with coverage and quality dashboard on same page.","keywords":["blue","dashboard","flame","flame-graph","graph","interpret","performance","profile","read","red","self-time","total-time","width","zoom"],"primaryTopics":[],"typicalErrors":[{"error":"E50001","correct":"transformed <- transformItem(item)","incorrect":"transformItem(item)","explanation":"Removing the variable declaration means later references to the variable become unresolved, triggering E50001. See ek9 -h E50001 for details."}],"companions":[]}
{"id":630,"category":"Profiling","question":"How do I identify hot methods in EK9 profiling output?","url":"https://ek9.io/qa/QA0630.html","alternatePhrasings":["How do I find the slowest functions in my EK9 program?","What is the hot function table in EK9 profiling?","How do I know which function to optimise first?"],"answer":"The profiler output includes a hot function table that lists functions sorted by self-time. The top entries are your optimisation targets.\n\nHOT FUNCTION TABLE\nThe -t6p HTML dashboard and -tp human-readable output both include a table with columns:\n- Function name: fully qualified (module::type.method or module::function).\n- Call count: how many times the function was invoked.\n- Self time: time spent in this function only.\n- Total time: time including all functions it calls.\n- Avg time: average time per call.\n- p95: 95th percentile call time.\n- p99: 99th percentile call time.\n\nSORTING STRATEGY\nSort by self-time to find functions doing the most work. Sort by call count to find functions called excessively. Sort by p99 to find functions with occasional slow calls (latency spikes).\n\nTHREE OPTIMISATION PATTERNS\n1. High self-time, low call count: the function itself is slow. Optimise its algorithm.\n2. Low self-time, very high call count: the function is fast but called too often. Reduce call frequency (cache results, batch operations).\n3. High p99 with low average: occasional slow path. Check for conditional branches that hit slow code paths intermittently.\n\nJSON FOR AUTOMATED ANALYSIS\n  ek9 -t2p myproject.ek9\nThe JSON output can be consumed by AI assistants or CI scripts to:\n- Flag functions where p99 exceeds a threshold.\n- Track self-time trends across builds.\n- Identify call count regressions.\n\nSee Q627 for profiling programs. See Q629 for reading flame graphs. See Q631 for benchmarking two approaches. See Q312 for complexity metrics. See Q322 for profiling overview.","ek9Example":"defines module qa.profilingdeep.hotmethods\n\n  defines function\n\n    <?-\n      Three functions with different profiling characteristics:\n      - expensiveComputation: high self-time (does real work).\n      - cheapHelper: low self-time, high call count.\n      - orchestrator: high total time, low self-time.\n    -?>\n    expensiveComputation() as pure\n      -> iterations as Integer\n      <- accumulator as Float: 0.0\n\n      for i in 1 ... iterations\n        term <- 1.0 / Float(i)\n        accumulator: accumulator + term\n\n    cheapHelper() as pure\n      -> number as Float\n      <- doubled as Float: number * 2.0\n\n    orchestrator()\n      -> limit as Integer\n      <- result as Float: 0.0\n\n      thousand <- 1000\n      raw <- expensiveComputation(thousand)\n      for i in 1 ... limit\n        result: cheapHelper(raw)\n\n  defines program\n\n    HotMethodsDemo()\n      stdout <- Stdout()\n\n      repetitions <- 50\n      finalValue <- orchestrator(repetitions)\n      stdout.println(`Result: ${finalValue}`)","migrationContext":"Java: JFR hot methods view in JMC, async-profiler flat profile. Python: cProfile cumulative/tottime columns, py-spy top view. Rust: perf report sorted by overhead. Go: go tool pprof top command. JavaScript: Chrome DevTools Bottom-Up view. EK9: built-in hot function table in -tp (text) and -t6p (HTML), sorted by self-time, with p95/p99 percentiles, JSON export for CI.","keywords":["call","count","flame-graph","function","hot","method","optimise","p95","p99","performance","profile","self-time","slow","sort","table"],"primaryTopics":[],"typicalErrors":[{"error":"E11051","correct":"result: cheapHelper(raw)","incorrect":"cheapHelper(raw)","explanation":"Calling a pure function without capturing its return value is dead code. Pure functions have no side effects. See ek9 -h E11051 for details."}],"companions":[]}
{"id":631,"category":"Profiling","question":"How do I benchmark two approaches in EK9?","url":"https://ek9.io/qa/QA0631.html","alternatePhrasings":["How do I compare the performance of two EK9 implementations?","Does EK9 have microbenchmarking support?","How do I benchmark execution time in EK9?"],"answer":"EK9 provides two complementary approaches to benchmarking: the built-in profiler for real-world measurement, and Millisecond/SystemClock for manual timing.\n\nAPPROACH 1: PROFILER COMPARISON\nWrite two test functions that exercise the two approaches, then profile:\n  ek9 -t2p myproject.ek9\nThe JSON output shows self-time and call count for each function. Compare the self-time values directly.\n\nAPPROACH 2: MANUAL TIMING\nUse SystemClock and Millisecond for explicit before/after measurement:\n  clock <- SystemClock()\n  before <- clock.millisecond()\n  // ... code to measure ...\n  after <- clock.millisecond()\n  elapsed <- after - before\nThis gives wall-clock elapsed time as a Millisecond value.\n\nBEST PRACTICES\n1. Warm up: run the code several times before measuring. JVM needs time to JIT-compile hot paths.\n2. Repeat: measure multiple iterations and compute the average. Single measurements are noisy.\n3. Isolate: benchmark one thing at a time. Do not mix the two approaches being compared in the same run.\n4. Use profiler for real code: manual timing is good for quick checks but the profiler provides p50/p95/p99 distributions.\n5. Avoid optimisation: profiling forces -O0 automatically. For manual timing, use ek9 -cd (debug mode, no optimisation) to prevent dead code elimination.\n\nWHEN TO USE EACH\n- Profiler (-tp/-t2p): for production-representative measurement with statistical detail.\n- Manual timing: for quick A/B comparison during development.\n- Both together: profile to find hot methods, then manually time the specific methods you want to optimise.\n\nSee Q627 for profiling programs. See Q628 for profiling tests. See Q630 for identifying hot methods. See Q544 for elapsed time and benchmarking with Millisecond. See Q41 for the Millisecond type.","ek9Example":"defines module qa.profilingdeep.benchmark\n\n  defines function\n\n    <?-\n      Two approaches to summing a range, to demonstrate benchmarking.\n      Approach 1: iterative loop.\n      Approach 2: mathematical formula.\n    -?>\n    sumIterative() as pure\n      -> limit as Integer\n      <- total as Integer: 0\n\n      for i in 1 ... limit\n        total: total + i\n\n    sumFormula() as pure\n      -> limit as Integer\n      <- total as Integer: limit * (limit + 1) / 2\n\n  defines program\n\n    BenchmarkDemo()\n      stdout <- Stdout()\n\n      testSize <- 1000\n\n      clock <- SystemClock()\n\n      beforeIterative <- clock.millisecond()\n      iterativeResult <- sumIterative(testSize)\n      afterIterative <- clock.millisecond()\n      iterativeElapsed <- afterIterative - beforeIterative\n\n      beforeFormula <- clock.millisecond()\n      formulaResult <- sumFormula(testSize)\n      afterFormula <- clock.millisecond()\n      formulaElapsed <- afterFormula - beforeFormula\n\n      stdout.println(`Iterative: ${iterativeResult} (${iterativeElapsed})`)\n      stdout.println(`Formula: ${formulaResult} (${formulaElapsed})`)","migrationContext":"Java: JMH (Java Microbenchmark Harness) with @Benchmark annotations, System.nanoTime() for manual timing. Python: timeit module, time.perf_counter() for manual timing. Rust: criterion crate for statistical benchmarks, std::time::Instant for manual. Go: testing.B benchmark framework, time.Now() for manual. JavaScript: performance.now() in browser, benchmark.js (external). EK9: built-in profiler with p95/p99 statistics via -tp/-t2p, SystemClock + Millisecond for manual timing, no external framework needed.","keywords":["Millisecond","SystemClock","approach","benchmark","compare","elapsed","flame-graph","measure","performance","profile","timing","warm"],"primaryTopics":[],"typicalErrors":[{"error":"E50001","correct":"iterativeResult <- sumIterative(testSize)","incorrect":"sumIterative(testSize)","explanation":"Removing the variable declaration means later references to the variable become unresolved, triggering E50001. See ek9 -h E50001 for details."}],"companions":[]}
{"id":632,"category":"Data Flow Safety","question":"How does EK9 ensure variables are used only after they are defined?","url":"https://ek9.io/qa/QA0632.html","alternatePhrasings":["What happens if I use a variable before declaring it?","Does EK9 check declaration order?","What is E08010 used before defined?"],"answer":"EK9 enforces sequential definition order within a scope. A variable must be declared before any reference to it. This prevents an entire category of bugs where code accidentally references a variable that does not yet exist.\n\nDEFINITION ORDER RULE\nWithin a function, method, or program body, statements execute top-to-bottom. A variable declared on line 10 cannot be referenced on line 8. The compiler checks this and raises E08010 if the reference precedes the declaration.\n\nWHY THIS MATTERS\nForward references to variables are a common copy-paste error. A developer moves a block of code above its variable declarations and the program silently reads uninitialized memory (in C) or gets undefined (in JavaScript). EK9 eliminates this by making definition order a compile-time guarantee.\n\nEXCEPTIONS\nTypes and functions can be forward-referenced because EK9 resolves them in a separate phase. Only local variables, parameters, and fields follow strict sequential order.\n\nCORRECT PATTERN\nDeclare each variable before using it. Group related declarations near their first use. The compiler verifies every reference has a preceding declaration in the same or enclosing scope.\n\nSee Q633 for initialization tracking across branches. See Q251 for debugging unset variable errors.\nSee Q688 for variable init order. See Q692 for field initialization.","ek9Example":"defines module qa.dataflow.definitionorder\n\n  defines function\n\n    <?-\n      Correct: each variable is declared before it is used.\n      taxRate is declared before taxAmount references it.\n    -?>\n    computeTotal() as pure\n      -> basePrice as Float\n      <- total as Float: basePrice\n\n      taxRate <- 0.15\n      taxAmount <- basePrice * taxRate\n      total: basePrice + taxAmount\n\n    <?-\n      Correct: intermediate results are computed in order.\n      Each step uses only previously declared variables.\n    -?>\n    computeDiscount() as pure\n      ->\n        originalPrice as Float\n        discountPercent as Float\n      <- finalPrice as Float: originalPrice\n\n      discountFraction <- discountPercent / 100.0\n      discountAmount <- originalPrice * discountFraction\n      finalPrice: originalPrice - discountAmount\n\n    <?-\n      Correct: variables defined in sequence, each referencing\n      only previously declared values.\n    -?>\n    buildGreeting() as pure\n      ->\n        firstName as String\n        lastName as String\n      <- greeting as String: firstName\n\n      fullName <- `${firstName} ${lastName}`\n      greeting: `Hello ${fullName}, welcome`\n\n  defines program\n\n    DefineBeforeUseDemo()\n      stdout <- Stdout()\n\n      totalPrice <- computeTotal(100.0)\n      stdout.println(`Total: ${totalPrice}`)\n\n      discounted <- computeDiscount(200.0, 15.0)\n      stdout.println(`Discounted: ${discounted}`)\n\n      msg <- buildGreeting(\"Alice\", \"Smith\")\n      stdout.println(msg)","migrationContext":"Java: variables must be declared before use (compiler error). JavaScript: var hoisting allows use before declaration (reads as undefined). Python: NameError at runtime if variable not yet assigned. C: undefined behavior if variable not initialized. Go: compile error for undeclared variable. Rust: compile error for uninitialized variable. EK9: compile error E08010 for any reference before declaration, enforced at Phase 3.","keywords":["E08010","before","data-flow","declaration","define","forward","initialize","migrate","order","reference","safety","scope","use","variable"],"primaryTopics":["define before use","variable initialization"],"typicalErrors":[{"error":"E08010","correct":"taxRate <- 0.15\n      taxAmount <- basePrice * taxRate","incorrect":"taxAmount <- basePrice * taxRate\n      taxRate <- 0.15","explanation":"Referencing taxRate before it is declared is a forward reference error. Variables must be declared before use. See ek9 -h E08010 for details."},{"error":"E50001","correct":"totalPrice <- computeTotal(100.0)","incorrect":"computeTotal(100.0)","explanation":"Removing the variable declaration means later references to the variable become unresolved, triggering E50001. See ek9 -h E50001 for details."}],"companions":[]}
{"id":633,"category":"Data Flow Safety","question":"How does EK9 track variable initialization across control flow branches?","url":"https://ek9.io/qa/QA0633.html","alternatePhrasings":["What happens if a variable is only initialized in one branch?","How does EK9 check initialization in if-else?","What is E08020 used before initialized?"],"answer":"EK9 tracks the initialization state of every variable through all control flow paths. If a variable might be uninitialized on any reachable path where it is used, the compiler raises E08020.\n\nBRANCH INITIALIZATION\nWhen a variable is assigned in both the if and else branches, the compiler knows it is initialized after the if-else regardless of which branch executes. If only one branch initializes the variable, the compiler considers it potentially uninitialized after the conditional.\n\nRETURN VARIABLE DEFAULTS\nReturn variables declared with a default value (e.g., '<- rtn as String: unknown') are always initialized. The default ensures the variable has a value even if no branch explicitly sets it.\n\nSAFE PATTERN\nEither provide a default value at declaration time, or ensure every branch of every conditional initializes the variable before it is used.\n\nSee Q632 for definition order. See Q634 for guard-based safe access. See Q251 for debugging unset variable errors.","ek9Example":"defines module qa.dataflow.branchinit\n\n  defines constant\n\n    EXCELLENT_THRESHOLD <- 90\n\n    GOOD_THRESHOLD <- 70\n\n    POOR_THRESHOLD <- 50\n\n  defines function\n\n    <?-\n      Correct: the return variable has a default value,\n      so it is always initialized regardless of branch taken.\n    -?>\n    classifyScore() as pure\n      -> score as Integer\n      <- category as String: \"average\"\n\n      if score >= EXCELLENT_THRESHOLD\n        category: \"excellent\"\n      else if score >= GOOD_THRESHOLD\n        category: \"good\"\n      else if score < POOR_THRESHOLD\n        category: \"poor\"\n\n    <?-\n      Correct: both branches explicitly set the return variable.\n      The compiler can verify both paths initialize it.\n    -?>\n    describeSign() as pure\n      -> number as Integer\n      <- description as String: \"zero\"\n\n      if number > 0\n        description: \"positive\"\n      else if number < 0\n        description: \"negative\"\n\n    <?-\n      Correct: intermediate variables are initialized at declaration\n      and used after initialization in both branches.\n    -?>\n    formatAmount() as pure\n      ->\n        amount as Float\n        showDecimals as Boolean\n      <- formatted as String: `${amount}`\n\n      rounded <- amount + 0.5\n      label <- \"Amount\"\n\n      if showDecimals\n        formatted: `${label}: ${amount}`\n      else\n        formatted: `${label}: ${rounded}`\n\n  defines program\n\n    BranchInitDemo()\n      stdout <- Stdout()\n\n      cat1 <- classifyScore(95)\n      stdout.println(`Score 95: ${cat1}`)\n\n      cat2 <- classifyScore(42)\n      stdout.println(`Score 42: ${cat2}`)\n\n      sign1 <- describeSign(5)\n      stdout.println(`5 is ${sign1}`)\n\n      sign2 <- describeSign(-3)\n      stdout.println(`-3 is ${sign2}`)\n\n      fmt1 <- formatAmount(19.99, true)\n      stdout.println(fmt1)\n\n      fmt2 <- formatAmount(19.99, false)\n      stdout.println(fmt2)","migrationContext":"Java: definite assignment analysis requires variables to be assigned before use, similar concept. Rust: borrow checker ensures initialization on all paths. Go: zero-value initialization means variables are always initialized (but may have wrong value). Python: runtime NameError if variable not assigned on taken path. C: undefined behavior for uninitialized variables. EK9: compile error E08020 for potentially uninitialized variables on any reachable path.","keywords":["E08020","branch","control","data-flow","else","flow","if","initialization","initialize","path","safety","uninitialized","variable"],"primaryTopics":[],"typicalErrors":[{"error":"E50001","correct":"category as String: \"average\"","incorrect":"category as String: String()\n      if score >= EXCELLENT_THRESHOLD\n        category: \"excellent\"\n      stdout.println(category)","explanation":"If category is only initialized in one branch, it may be uninitialized when used later. Provide a default value at declaration or initialize on all paths. See ek9 -h E50001 for details."}],"companions":[]}
{"id":634,"category":"Data Flow Safety","question":"Why must I use a guard expression before accessing a returned value?","url":"https://ek9.io/qa/QA0634.html","alternatePhrasings":["How do guard expressions prevent unsafe access?","What happens if I skip the guard on a function return?","What is E08030 unsafe method access?"],"answer":"EK9 requires guard expressions to verify that a value is set before calling methods on it. Without the guard, the value might be unset, and calling a method on an unset value is unsafe.\n\nGUARD EXPRESSION PATTERN\nThe guard pattern 'if value <- expression()' combines assignment with an isSet check. The variable is only in scope inside the guarded block, where it is guaranteed to be set. This eliminates unsafe access by construction.\n\nWHY GUARDS ARE NEEDED\nFunctions can return unset values (e.g., an empty Optional, a failed lookup). Without a guard, calling methods on an unset value would be like calling methods on null in Java. EK9 prevents this at compile time rather than crashing at runtime.\n\nMULTIPLE GUARD PATTERNS\nGuards work identically in if, while, switch, for, and try statements. The same syntax 'var <- expr()' checks isSet and creates a scoped variable.\n\nALTERNATIVE: EXPLICIT isSet CHECK\nYou can also check with the ? operator: 'if someValue?' then use someValue inside the block. The compiler tracks that ? was checked.\n\nSee Q163 for Optional unwrapping patterns. See Q632 for definition ordering. See Q633 for branch initialization. See Q636 for chained guard access. See Q47 for Optional basics.\nSee Q687 for Result guard access. See Q688 for variable init order.","ek9Example":"defines module qa.dataflow.guardaccess\n\n  defines constant\n\n    ALICE_ID <- 1\n\n    BOB_ID <- 2\n\n    STUDENT_ALPHA <- 100\n\n    STUDENT_BETA <- 200\n\n    UNKNOWN_ID <- 999\n\n  defines function\n\n    <?-\n      Simulates a lookup that might return an unset value.\n    -?>\n    findUserName()\n      -> userId as Integer\n      <- userName as String: String()\n\n      if userId == ALICE_ID\n        userName: \"Alice\"\n      else if userId == BOB_ID\n        userName: \"Bob\"\n\n    <?-\n      Simulates a function that always returns a set value.\n    -?>\n    getDefaultGreeting()\n      <- greeting as String: \"Hello\"\n\n    <?-\n      Simulates a numeric lookup that may return unset.\n    -?>\n    lookupScore()\n      -> studentId as Integer\n      <- score as Float: Float()\n\n      if studentId == STUDENT_ALPHA\n        score: 95.5\n      else if studentId == STUDENT_BETA\n        score: 87.3\n\n  defines program\n\n    GuardBeforeAccessDemo()\n      stdout <- Stdout()\n\n      //Guard pattern: variable only accessible when set\n      if name <- findUserName(ALICE_ID)\n        upperName <- name.upperCase()\n        stdout.println(`Found user: ${upperName}`)\n\n      //Guard on a function that may return unset\n      if name2 <- findUserName(UNKNOWN_ID)\n        stdout.println(`Found: ${name2}`)\n      else\n        stdout.println(\"User not found\")\n\n      //Multiple guards in sequence\n      if score <- lookupScore(STUDENT_ALPHA)\n        doubled <- score * 2.0\n        stdout.println(`Score doubled: ${doubled}`)\n\n      //Explicit isSet check pattern\n      greeting <- getDefaultGreeting()\n      if greeting?\n        stdout.println(greeting)\n\n      //Nested guard: both must be set\n      if userName <- findUserName(BOB_ID)\n        if userScore <- lookupScore(STUDENT_BETA)\n          stdout.println(`${userName} scored ${userScore}`)","migrationContext":"Java: no guard expressions, Optional.get() throws NoSuchElementException, nullable references crash with NPE. Kotlin: safe call ?. operator returns null on unset, Elvis ?: for defaults. Swift: if let/guard let unwraps optionals safely. Rust: if let Some(v) = expr pattern matching. Go: manual nil checks. EK9: guard expression 'if var <- expr()' enforced by compiler, variable only in scope when set.","keywords":["E08030","E08040","absent","access","data-flow","expression","guard","initialize","isSet","isset","method","migrate","null","null-safe","optional","safe","safety","unset"],"primaryTopics":[],"typicalErrors":[{"error":"E50001","correct":"if name <- findUserName(ALICE_ID)\n        upperName <- name.upperCase()","incorrect":"upperName <- name.upperCase()\n      if name <- findUserName(ALICE_ID)","explanation":"Referencing name before its guard declaration is a forward reference error. The guard must come before any use. See ek9 -h E50001 for details."}],"companions":[]}
{"id":635,"category":"Data Flow Safety","question":"What operations are forbidden inside a pure method?","url":"https://ek9.io/qa/QA0635.html","alternatePhrasings":["Why does EK9 reject mutation operators in pure methods?","What is E08120 mutation in pure context?","Can I use += inside a pure function?"],"answer":"Pure methods and functions are forbidden from using any mutation operator. This is enforced at compile time as E08120.\n\nFORBIDDEN MUTATION OPERATORS IN PURE CONTEXT\nThe following operators are mutation operators and cannot appear inside a pure method or function: += (add-assign), -= (subtract-assign), *= (multiply-assign), /= (divide-assign), :=: (copy-into), :~: (merge-into), :^: (replace-with). Using any of these inside a pure method triggers E08120.\n\nALLOWED IN PURE CONTEXT\nReassignment with : (colon) is allowed because it creates a new binding. Non-mutating operators (+, -, *, /, ==, <>) are allowed. Creating new values and returning them is allowed. Reading fields and parameters is allowed.\n\nWHY THIS RESTRICTION EXISTS\nPure methods guarantee no side effects. If a pure method could use +=, it would mutate the state of an existing object, breaking the purity contract. The compiler enforces this so callers can trust that pure methods do not change any state.\n\nREASSIGNMENT VS MUTATION\nReassignment (variable: newValue) binds the variable to a new value. Mutation (variable += delta) modifies the existing object in place. Pure methods allow reassignment but forbid mutation.\n\nSee Q560 for pure method basics. See Q566 for pure call chain restrictions. See Q273 for purity as a security boundary.","ek9Example":"defines module qa.dataflow.puremutation\n\n  defines class\n\n    Account\n      balance <- 0.0\n      accountName <- \"unnamed\"\n\n      Account()\n        ->\n          accountName as String\n          initialBalance as Float\n        this.accountName: accountName\n        this.balance: initialBalance\n\n      <?-\n        Pure: computes new balance without mutating the field.\n        Uses + operator (non-mutating) and : reassignment (allowed).\n      -?>\n      projectedBalance() as pure\n        -> deposit as Float\n        <- projected as Float: balance + deposit\n\n      <?-\n        Pure: builds a formatted string from fields.\n        No mutation operators used.\n      -?>\n      formatSummary() as pure\n        <- summary as String: `${accountName}: ${balance}`\n\n      <?-\n        Non-pure: uses mutation operator to modify balance.\n        This method correctly omits 'as pure'.\n      -?>\n      deposit()\n        -> amount as Float\n        balance += amount\n\n      <?-\n        Non-pure: uses mutation operator.\n      -?>\n      withdraw()\n        -> amount as Float\n        balance -= amount\n\n      default operator ?\n\n  defines program\n\n    PureMutationDemo()\n      stdout <- Stdout()\n\n      acct <- Account(\"Savings\", 1000.0)\n      stdout.println(acct.formatSummary())\n\n      //Pure call: safe, no mutation\n      projected <- acct.projectedBalance(500.0)\n      stdout.println(`Projected: ${projected}`)\n\n      //Non-pure: mutates the account\n      acct.deposit(500.0)\n      stdout.println(acct.formatSummary())\n\n      acct.withdraw(200.0)\n      stdout.println(acct.formatSummary())","migrationContext":"Java: no compile-time purity enforcement. Python: no immutability guarantees. Haskell: all functions pure by default. Rust: mutable borrows are explicit. Kotlin: val prevents reassignment but does not prevent method-level mutation. EK9: E08120 rejects mutation operators in pure methods at compile time.","keywords":["E08120","assign","copy","data-flow","forbidden","function","immutable","initialize","merge","migrate","mutation","operator","pure","restriction","safety","side-effect"],"primaryTopics":[],"typicalErrors":[{"error":"E50060","correct":"stdout.println(acct.formatSummary())","incorrect":"stdout.println(acct.formatSummary().toUpperCase())","explanation":"String has no toUpperCase() method in EK9. Use upperCase() instead. See ek9 -h E50060 for details."},{"error":"E50001","correct":"projected <- acct.projectedBalance(500.0)","incorrect":"acct.projectedBalance(500.0)","explanation":"Removing the variable declaration means later references to the variable become unresolved, triggering E50001. See ek9 -h E50001 for details."}],"companions":[]}
{"id":636,"category":"Data Flow Safety","question":"How do I chain multiple guard expressions for safe access?","url":"https://ek9.io/qa/QA0636.html","alternatePhrasings":["Can I nest guard expressions in EK9?","How do I safely access multiple potentially-unset values?","What is the pattern for multiple optional lookups?"],"answer":"When you need to access multiple values that might be unset, chain guard expressions by nesting if-guards or using sequential guards with explicit isSet checks.\n\nNESTED GUARDS\nNest guard expressions to ensure all values are set:\n  if name <- findName(id)\n    if score <- findScore(id)\n      result: formatResult(name, score)\nEach level guarantees its variable is set before the inner block executes.\n\nSEQUENTIAL GUARDS\nUse separate guard expressions in sequence when values are independent:\n  if name <- findName(id)\n    stdout.println(name)\n  if score <- findScore(id)\n    stdout.println(score)\nEach guard independently checks its value.\n\nGUARD WITH ELSE\nProvide fallback behavior when a guard fails:\n  if name <- findName(id)\n    greet(name)\n  else\n    greetStranger()\n\nWHY CHAIN GUARDS\nChaining guards ensures that every value used in a computation is guaranteed set. The compiler verifies each guard protects its variable scope. Without the guard, accessing the value would be unsafe (E08030).\n\nSee Q634 for guard basics. See Q163 for Optional unwrapping. See Q47 for Optional type patterns.","ek9Example":"defines module qa.dataflow.chainedguard\n\n  defines constant\n\n    USER_ALICE <- 1\n\n    USER_CHARLIE <- 3\n\n    UNKNOWN_USER <- 999\n\n  defines function\n\n    findFirstName()\n      -> userId as Integer\n      <- rtn as String: String()\n\n      if userId == USER_ALICE\n        rtn: \"Alice\"\n      else if userId == USER_CHARLIE\n        rtn: \"Charlie\"\n\n    findLastName()\n      -> userId as Integer\n      <- rtn as String: String()\n\n      if userId == USER_ALICE\n        rtn: \"Smith\"\n      else if userId == USER_CHARLIE\n        rtn: \"Brown\"\n\n    findAge()\n      -> userId as Integer\n      <- rtn as Integer: Integer()\n\n      if userId == USER_ALICE\n        rtn: 30\n      else if userId == USER_CHARLIE\n        rtn: 25\n\n    findDepartment()\n      -> userId as Integer\n      <- rtn as String: String()\n\n      if userId == USER_ALICE\n        rtn: \"Engineering\"\n\n  defines program\n\n    ChainedGuardDemo()\n      stdout <- Stdout()\n\n      //Nested guards: all must be set to build full profile\n      if first <- findFirstName(USER_ALICE)\n        if last <- findLastName(USER_ALICE)\n          if age <- findAge(USER_ALICE)\n            stdout.println(`${first} ${last}, age ${age}`)\n\n      //Sequential independent guards\n      if nameA <- findFirstName(USER_ALICE)\n        stdout.println(`Found: ${nameA}`)\n\n      if nameB <- findFirstName(UNKNOWN_USER)\n        stdout.println(`Found: ${nameB}`)\n      else\n        stdout.println(\"User not found\")\n\n      //Guard with additional nested guard\n      if dept <- findDepartment(USER_ALICE)\n        if userName <- findFirstName(USER_ALICE)\n          stdout.println(`${userName} works in ${dept}`)\n\n      //Explicit isSet check pattern\n      ageResult <- findAge(USER_CHARLIE)\n      if ageResult?\n        stdout.println(`Age found: ${ageResult}`)","migrationContext":"Java: nested if-present checks on Optional, pyramid of doom. Kotlin: safe call chaining ?. operator, scope functions let/run. Swift: if let chaining, optional binding. Rust: pattern matching with nested Some/None. Go: repeated nil checks. EK9: nested guard expressions 'if var <- expr()' with compiler-enforced scoping.","keywords":["E08030","E08040","absent","access","chain","data-flow","guard","initialize","isset","multiple","nested","null-safe","optional","safe","safety","scope"],"primaryTopics":[],"typicalErrors":[{"error":"E50001","correct":"if first <- findFirstName(USER_ALICE)\n        if last <- findLastName(USER_ALICE)","incorrect":"stdout.println(`${first} ${last}`)\n      if first <- findFirstName(USER_ALICE)","explanation":"Using first before its guard declaration is a forward reference error. The guard must be evaluated before using the variable. See ek9 -h E50001 for details."}],"companions":[]}
{"id":637,"category":"Comparison Patterns","question":"How should I compare two similar variables to avoid self-comparison errors?","url":"https://ek9.io/qa/QA0637.html","alternatePhrasings":["What is self-comparison and how does EK9 detect it?","Why does EK9 reject comparing a variable to itself?","How do I fix E08081 self-comparison?"],"answer":"EK9 detects when both sides of a comparison operator resolve to the same variable and raises E08081. This is always a copy-paste error.\n\nTHE PROBLEM\nWhen duplicating comparison code, it is easy to write 'priceA == priceA' instead of 'priceA == priceB'. The result is a constant (always true for ==, always false for <>) and the intended comparison never happens. This bug is silent in most languages.\n\nFLAGGED OPERATORS\nSelf-comparison is detected for: ==, <>, <, >, <=, >=, <=>, <~>, contains, and matches. All produce constant results when both operands are the same variable.\n\nCORRECT PATTERN\nAlways compare two DIFFERENT variables. Use distinct meaningful names that make the intent clear:\n  if currentPrice < previousPrice\n  if scoreA == scoreB\n  if leftValue <=> rightValue\n\nCOMMON MISTAKE\nCopy-pasting a line like 'if priceA < priceB' and accidentally getting 'if priceA < priceA' when modifying. EK9 catches this immediately.\n\nSee Q318 for self-assignment detection. See Q558 for tautological conditions. See Q638 for named constant patterns. See Q639 for Boolean usage without literals.","ek9Example":"defines module qa.comparison.safecompare\n\n  defines function\n\n    <?-\n      Correct: compares two different variables (priceA vs priceB).\n      Renaming one operand to match the other would trigger E08081.\n    -?>\n    findCheaperItem() as pure\n      ->\n        priceA as Float\n        priceB as Float\n      <- cheaperPrice as Float: priceA\n\n      if priceB < priceA\n        cheaperPrice: priceB\n\n    <?-\n      Correct: compares two distinct score variables.\n      Each operand is a different parameter.\n    -?>\n    scoresMatch() as pure\n      ->\n        scoreA as Integer\n        scoreB as Integer\n      <- matched as Boolean: scoreA == scoreB\n\n    <?-\n      Correct: compares left and right boundary values.\n      Distinct names make the intent clear.\n    -?>\n    isOverlapping() as pure\n      ->\n        leftStart as Integer\n        leftEnd as Integer\n        rightStart as Integer\n        rightEnd as Integer\n      <- overlapping as Boolean: leftStart <= rightEnd\n\n      if leftEnd < rightStart\n        overlapping: false\n      else if rightEnd < leftStart\n        overlapping: false\n\n    <?-\n      Correct: three-way comparison between distinct amounts.\n    -?>\n    findMiddleValue() as pure\n      ->\n        firstAmount as Float\n        secondAmount as Float\n        thirdAmount as Float\n      <- middleAmount as Float: firstAmount\n\n      if firstAmount >= secondAmount and firstAmount <= thirdAmount\n        middleAmount: firstAmount\n      else if secondAmount >= firstAmount and secondAmount <= thirdAmount\n        middleAmount: secondAmount\n      else\n        middleAmount: thirdAmount\n\n  defines program\n\n    SafeComparisonDemo()\n      stdout <- Stdout()\n\n      cheaper <- findCheaperItem(29.99, 19.99)\n      stdout.println(`Cheaper: ${cheaper}`)\n\n      matched <- scoresMatch(85, 85)\n      stdout.println(`Scores match: ${matched}`)\n\n      overlap <- isOverlapping(leftStart: 1, leftEnd: 5, rightStart: 3, rightEnd: 8)\n      stdout.println(`Overlapping: ${overlap}`)\n\n      mid <- findMiddleValue(3.0, 1.0, 2.0)\n      stdout.println(`Middle: ${mid}`)","migrationContext":"Java: no self-comparison detection in javac. SpotBugs has limited checks. SonarQube detects some patterns. Rust: clippy eq_op lint warns on self-comparison. Go: go vet detects some self-assignments. Python: no built-in detection. C++: -Wtautological-compare warns on some patterns. EK9: mandatory compiler error E08081 for all comparison and containment operators.","keywords":["E08081","comparison","copy","debug","detect","different","equal","pair","paste","pattern","self","variable"],"primaryTopics":[],"typicalErrors":[{"error":"E08081","correct":"if priceB < priceA","incorrect":"if priceA < priceA","explanation":"Comparing a variable to itself always produces a constant result. This is a copy-paste error where both operands resolve to the same variable. See ek9 -h E08081 for details."},{"error":"E08081","correct":"matched as Boolean: scoreA == scoreB","incorrect":"matched as Boolean: scoreA == scoreA","explanation":"Self-comparison with == always produces true. Use two different variables to make a meaningful comparison. See ek9 -h E08081 for details."}],"companions":[]}
{"id":638,"category":"Comparison Patterns","question":"Why should I use named constants instead of literal values in comparisons?","url":"https://ek9.io/qa/QA0638.html","alternatePhrasings":["Why does EK9 reject literal-vs-literal comparisons?","Why does EK9 reject comparing two literal values?","How do I fix a magic number comparison error?"],"answer":"EK9 detects when both sides of a comparison are literal values and raises E08082. The result is always a compile-time constant, making the comparison pointless.\n\nTHE PROBLEM\nComparing two literals like '18 > 10' always produces the same result. This is dead code: the condition is predetermined. It typically indicates that the developer meant to compare a variable against a constant but accidentally used two constants.\n\nCORRECT PATTERN\nUse a named constant on one side and a variable on the other:\n  if age >= MINIMUM_AGE\n  if temperature > BOILING_POINT\n  if count == EXPECTED_COUNT\n\nNAMED CONSTANTS\nDeclare constants in a defines constant block:\n  defines constant\n    MINIMUM_AGE <- 18\n    MAXIMUM_SPEED <- 120\nThis gives the literal a meaningful name and avoids magic numbers throughout the code.\n\nWHY NAMED CONSTANTS MATTER\nSelf-documenting: 'MINIMUM_AGE' explains the intent. Single source of truth: change the value in one place. Avoids E08082: variable vs constant is a genuine comparison. Avoids E11065: magic literals in comparisons.\n\nSee Q637 for self-comparison detection. See Q558 for tautological conditions. See Q639 for Boolean comparison patterns.","ek9Example":"defines module qa.comparison.namedconstants\n\n  defines constant\n\n    MINIMUM_AGE <- 18\n\n    MAXIMUM_AGE <- 120\n\n    PASSING_SCORE <- 60\n\n    SPEED_LIMIT <- 65\n\n    BOILING_POINT <- 100.0\n\n    FREEZING_POINT <- 0.0\n\n  defines function\n\n    <?-\n      Correct: compares a variable against a named constant.\n      Replacing the variable with a literal would trigger E08082.\n    -?>\n    isAdult() as pure\n      -> age as Integer\n      <- adult as Boolean: age >= MINIMUM_AGE\n\n    <?-\n      Correct: variable compared against named constant.\n    -?>\n    isPassing() as pure\n      -> score as Integer\n      <- passing as Boolean: score >= PASSING_SCORE\n\n    <?-\n      Correct: variable compared against named constants for range check.\n    -?>\n    isValidAge() as pure\n      -> age as Integer\n      <- valid as Boolean: age >= MINIMUM_AGE\n\n      if age > MAXIMUM_AGE\n        valid: false\n\n    <?-\n      Correct: Float variable compared against named constant.\n    -?>\n    isBoiling() as pure\n      -> temperature as Float\n      <- boiling as Boolean: temperature >= BOILING_POINT\n\n    <?-\n      Correct: uses named constants for range classification.\n    -?>\n    classifyTemperature() as pure\n      -> temperature as Float\n      <- classification as String: \"normal\"\n\n      if temperature >= BOILING_POINT\n        classification: \"boiling\"\n      else if temperature <= FREEZING_POINT\n        classification: \"freezing\"\n\n  defines program\n\n    NamedConstantsDemo()\n      stdout <- Stdout()\n\n      stdout.println(`Is 21 adult? ${isAdult(21)}`)\n      stdout.println(`Is 15 adult? ${isAdult(15)}`)\n\n      stdout.println(`Score 75 passing? ${isPassing(75)}`)\n      stdout.println(`Score 40 passing? ${isPassing(40)}`)\n\n      stdout.println(`Age 25 valid? ${isValidAge(25)}`)\n      stdout.println(`Age 150 valid? ${isValidAge(150)}`)\n\n      stdout.println(`100C boiling? ${isBoiling(100.0)}`)\n      stdout.println(`50C boiling? ${isBoiling(50.0)}`)\n\n      cls <- classifyTemperature(105.0)\n      stdout.println(`105C: ${cls}`)","migrationContext":"Java: no compile-time constant comparison detection. SonarQube flags magic numbers as code smell (optional). Rust: clippy has absurd_extreme_comparisons lint. Go: no magic number detection. Python: pylint magic-value-comparison. C++: -Wtautological-compare for some literal comparisons. EK9: E08082 mandatory error for literal-vs-literal comparison, E11065 for magic literals.","keywords":["E08082","E11065","comparison","constant","dead","literal","magic","migrate","named","number","pattern","value"],"primaryTopics":[],"typicalErrors":[{"error":"E08082","correct":"adult as Boolean: age >= MINIMUM_AGE","incorrect":"adult as Boolean: 18 >= 18","explanation":"Comparing two literal values produces a compile-time constant, making the comparison dead code. Compare a variable against a named constant instead. See ek9 -h E08082 for details."},{"error":"E11064","correct":"if age > MAXIMUM_AGE","incorrect":"if age > 120","explanation":"Using a raw literal 120 in a comparison is a magic literal. Use the named constant MAXIMUM_AGE instead. See ek9 -h E11064 for details."}],"companions":[]}
{"id":639,"category":"Comparison Patterns","question":"Should I compare Boolean values with == true or == false?","url":"https://ek9.io/qa/QA0639.html","alternatePhrasings":["What is E08083 redundant Boolean comparison?","Why does EK9 reject 'if flag == true'?","How should I use Boolean values in conditions?"],"answer":"EK9 detects redundant Boolean comparisons and raises E08083. Comparing a Boolean to a literal (true or false) is always redundant because the Boolean IS the condition.\n\nTHE PROBLEM\n'if ready == true' is equivalent to 'if ready'. The '== true' adds nothing. Similarly, 'if ready == false' is equivalent to 'if not ready' (using Boolean negation). The extra comparison is dead code.\n\nCORRECT PATTERNS\nDirect use: 'if isReady' instead of 'if isReady == true'\nNegation: 'if not isReady' instead of 'if isReady == false'\nCombined: 'if isReady and isValid' instead of 'if isReady == true and isValid == true'\n\nWHY THIS IS FLAGGED\nRedundant Boolean comparisons indicate unclear thinking about Boolean logic. They also create opportunities for copy-paste errors: 'if x == true and y == true' could accidentally become 'if x == true and x == true' (self-comparison plus redundant comparison).\n\nBOOLEAN OPERATORS\nUse Boolean operators directly: and, or, not, xor. These express intent clearly without comparing to literals.\n\nSee Q637 for self-comparison detection. See Q638 for named constant patterns. See Q558 for tautological conditions.","ek9Example":"defines module qa.comparison.booleanuse\n\n  defines function\n\n    <?-\n      Correct: Boolean value used directly in condition.\n      Adding '== true' would trigger E08083.\n    -?>\n    checkEligibility() as pure\n      ->\n        isAdult as Boolean\n        hasConsent as Boolean\n      <- eligible as Boolean: isAdult and hasConsent\n\n    <?-\n      Correct: Boolean negation using 'not' operator.\n      Writing 'isBlocked == false' would trigger E08083.\n    -?>\n    canProceed() as pure\n      ->\n        isBlocked as Boolean\n        isReady as Boolean\n      <- proceed as Boolean: not isBlocked and isReady\n\n    <?-\n      Correct: combines multiple Boolean values directly.\n      No comparison to literals needed.\n    -?>\n    classifyAccess() as pure\n      ->\n        isAuthenticated as Boolean\n        isAuthorized as Boolean\n        isActive as Boolean\n      <- accessLevel as String: \"denied\"\n\n      if isAuthenticated and isAuthorized and isActive\n        accessLevel: \"full\"\n      else if isAuthenticated and isActive\n        accessLevel: \"limited\"\n      else if isAuthenticated\n        accessLevel: \"readonly\"\n\n    <?-\n      Correct: Boolean return from comparison, used directly.\n    -?>\n    isInRange() as pure\n      ->\n        testValue as Integer\n        minBound as Integer\n        maxBound as Integer\n      <- inRange as Boolean: testValue >= minBound and testValue <= maxBound\n\n  defines program\n\n    BooleanWithoutLiteralsDemo()\n      stdout <- Stdout()\n\n      //Use variables for Boolean arguments\n      adult <- true\n      consented <- true\n      eligible <- checkEligibility(isAdult: adult, hasConsent: consented)\n      stdout.println(`Eligible: ${eligible}`)\n\n      blocked <- false\n      ready <- true\n      proceed <- canProceed(isBlocked: blocked, isReady: ready)\n      stdout.println(`Can proceed: ${proceed}`)\n\n      authenticated <- true\n      authorized <- true\n      active <- true\n      lvl <- classifyAccess(isAuthenticated: authenticated, isAuthorized: authorized, isActive: active)\n      stdout.println(`Access: ${lvl}`)\n\n      inRange <- isInRange(testValue: 5, minBound: 1, maxBound: 10)\n      stdout.println(`In range: ${inRange}`)","migrationContext":"Java: no detection of redundant boolean comparison. SonarQube flags 'if (x == true)' as minor code smell. Rust: clippy has bool_comparison lint (warn). Go: no detection. Python: pylint has C0121 singleton-comparison. Kotlin: detects some redundant boolean comparisons. EK9: mandatory compile error E08083 for comparing Boolean to literal.","keywords":["E08083","E08084","boolean","comparison","condition","direct","false","literal","pattern","redundant","true"],"primaryTopics":[],"typicalErrors":[{"error":"E08084","correct":"if isAuthenticated and isAuthorized and isActive","incorrect":"if isAuthenticated == true and isAuthorized == true and isActive == true","explanation":"Comparing a Boolean to the literal true is redundant. The Boolean IS the condition. Use it directly. See ek9 -h E08084 for details."},{"error":"E08084","correct":"proceed as Boolean: not isBlocked and isReady","incorrect":"proceed as Boolean: isBlocked == false and isReady == true","explanation":"Comparing Booleans to literal values is redundant. Use not for negation and the Boolean directly for truth. See ek9 -h E08084 for details."}],"companions":[]}
{"id":640,"category":"Comparison Patterns","question":"When is an isSet check on a collection genuinely needed?","url":"https://ek9.io/qa/QA0640.html","alternatePhrasings":["Is checking a list with ? redundant after construction?","What is E08088 redundant isSet check on collection?","When should I check if a collection is set?"],"answer":"Collections (List, Dict) are always set after construction, even when empty. Checking a newly constructed collection with ? is always redundant (E08088). However, when a collection comes from a function return, the check may be genuinely needed.\n\nWHEN ISSET IS REDUNDANT (E08088)\nAfter constructing a collection directly:\n  items <- List() of String\n  if items?    <- this is ALWAYS true (redundant)\nThe compiler knows the constructor always returns a set value.\n\nWHEN ISSET IS NEEDED\nWhen the collection comes from a function that might return unset:\n  if results <- searchItems(query)\n    process(results)\nThe function might return an unset List to signal 'no results found'.\n\nEMPTY VS UNSET\nAn empty list (0 items) is SET. It exists, it is valid, it just has no elements. An unset list does not yet have a meaningful value. These are different states. Use 'empty' to check for no elements, use '?' to check for existence.\n\nCORRECT PATTERN FOR EMPTY CHECK\nUse the empty operator to check for no elements:\n  if items empty\n    stdout.println('No items')\nDo NOT use '?' to check if a list has elements.\n\nSee Q559 for redundant isSet detection. See Q558 for tautological conditions. See Q641 for return value capture.","ek9Example":"defines module qa.comparison.collectionisset\n\n  defines function\n\n    <?-\n      Simulates a search that might return an unset list.\n    -?>\n    searchByKeyword()\n      -> keyword as String\n      <- results as List of String: List() of String\n\n      if keyword == \"ek9\"\n        results += \"Introduction to EK9\"\n        results += \"EK9 Best Practices\"\n\n    <?-\n      Simulates a lookup that might return unset.\n    -?>\n    findScores()\n      -> studentId as Integer\n      <- scores as List of Integer: List() of Integer\n\n      if studentId == 1\n        scores += 85\n        scores += 92\n        scores += 78\n\n    <?-\n      Pure function that processes a list.\n    -?>\n    countAboveThreshold() as pure\n      ->\n        items as List of Integer\n        threshold as Integer\n      <- counted as Integer: 0\n\n      for item in items\n        if item > threshold\n          counted: counted + 1\n\n  defines program\n\n    CollectionIsSetDemo()\n      stdout <- Stdout()\n\n      //Guard on function return: genuinely needed\n      if results <- searchByKeyword(\"ek9\")\n        stdout.println(`Found ${length results} results`)\n        for item in results\n          stdout.println(`  ${item}`)\n\n      //Guard on function that might return unset\n      if scores <- findScores(1)\n        above80 <- countAboveThreshold(scores, 80)\n        stdout.println(`Scores above 80: ${above80}`)\n\n      //No results case\n      if noResults <- searchByKeyword(\"unknown\")\n        stdout.println(\"Should not print\")\n      else\n        stdout.println(\"No results for 'unknown'\")\n\n      //Empty check: appropriate when list IS set but might have no items\n      knownScores <- findScores(1)\n      if knownScores?\n        if knownScores empty\n          stdout.println(\"Empty scores\")\n        else\n          stdout.println(`Has ${length knownScores} scores`)","migrationContext":"Java: no tracking of collection initialization state. Kotlin: nullable collection vs empty collection distinguished by type. Rust: Option<Vec<T>> vs empty Vec<T>. Go: nil slice vs empty slice. Python: None vs empty list. EK9: tri-state (absent, unset, set), collections are always set after construction, E08088 for redundant isSet check.","keywords":["E08088","E08089","check","collection","compare","comparison","constructed","dict","empty","isSet","list","pattern","redundant"],"primaryTopics":[],"typicalErrors":[{"error":"E50001","correct":"above80 <- countAboveThreshold(scores, 80)","incorrect":"countAboveThreshold(scores, 80)","explanation":"Removing the variable declaration means later references to the variable become unresolved, triggering E50001. See ek9 -h E50001 for details."}],"companions":[]}
{"id":641,"category":"Comparison Patterns","question":"Why must I capture the return value of a pure function call?","url":"https://ek9.io/qa/QA0641.html","alternatePhrasings":["What is E11050 discarded return value?","What is E11051 discarded pure function return?","Why does EK9 reject calling a function without capturing its result?"],"answer":"EK9 detects when a function with a return value is called as a bare statement and the return value is discarded. For pure functions this is always an error (E11051) because a pure function has no side effects, so calling it without using the result is completely pointless.\n\nDISCARDED RETURN VALUE (E11050)\nCalling a function that returns a value without capturing the result:\n  computeTotal(items)    <- discards the returned total\nThe function was called, but its result was thrown away. This usually means the developer forgot the assignment.\n\nDISCARDED PURE RETURN (E11051)\nCalling a pure function as a bare statement is especially bad because pure functions have NO side effects. The call does nothing observable. It is dead code.\n\nDISCARDED CONSTRUCTOR (E11055)\nCreating an object without capturing the reference:\n  Sensor(98.6)    <- creates and immediately discards\nThis is always a bug. If you need side effects, use a function.\n\nDISCARDED RESULT OR OPTIONAL (E11052)\nCalling a function that returns Result or Optional and ignoring the result defeats the purpose of these types.\n\nCORRECT PATTERN\nAlways capture the return value of functions that return values:\n  total <- computeTotal(items)\n  processed <- transform(input)\n  newList <- List() of String\n\nSee Q316 for discarded return detection. See Q640 for collection patterns. See Q635 for pure mutation restrictions.","ek9Example":"defines module qa.comparison.capturereturn\n\n  defines constant\n\n    SUM_THRESHOLD <- 6\n\n  defines function\n\n    <?-\n      Pure function: its return value MUST be captured.\n      Calling this without capturing triggers E11051.\n    -?>\n    computeSum() as pure\n      ->\n        valueA as Integer\n        valueB as Integer\n      <- total as Integer: valueA + valueB\n\n    <?-\n      Pure function: return value must be captured.\n    -?>\n    formatPrice() as pure\n      -> price as Float\n      <- formatted as String: `Price: ${price}`\n\n    <?-\n      Pure function with string processing.\n    -?>\n    buildLabel() as pure\n      ->\n        prefix as String\n        suffix as String\n      <- label as String: `${prefix}-${suffix}`\n\n    <?-\n      Non-pure function with return value.\n      Return should still be captured (E11050).\n    -?>\n    fetchAndLog()\n      -> itemId as Integer\n      <- itemName as String: `item-${itemId}`\n\n  defines program\n\n    CaptureReturnDemo()\n      stdout <- Stdout()\n\n      //Correct: capture the return value\n      total <- computeSum(10, 20)\n      stdout.println(`Sum: ${total}`)\n\n      //Correct: capture formatted string\n      priceStr <- formatPrice(9.99)\n      stdout.println(priceStr)\n\n      //Correct: capture and use\n      label <- buildLabel(\"order\", \"001\")\n      stdout.println(label)\n\n      //Correct: capture non-pure return\n      itemName <- fetchAndLog(42)\n      stdout.println(`Fetched: ${itemName}`)\n\n      //Correct: use return value directly in expression\n      doubled <- computeSum(5, 5) * 2\n      stdout.println(`Doubled: ${doubled}`)\n\n      //Correct: use in condition\n      if computeSum(3, 4) > SUM_THRESHOLD\n        stdout.println(\"Sum exceeds threshold\")","migrationContext":"Java: return values can always be silently discarded, no warning by default. IntelliJ warns on discarded return from pure-annotated methods (optional). Rust: #[must_use] attribute warns when return value is discarded (opt-in). Go: error return values often silently discarded (common bug source). Kotlin: no enforcement. C++: [[nodiscard]] attribute (C++17, opt-in). EK9: mandatory compiler error E11050/E11051 for discarded return values, especially pure functions.","keywords":["E11050","E11051","E11052","E11055","capture","compare","comparison","constructor","dead","discard","error","function","guard","immutable","ok","pattern","pure","result","return","side-effect"],"primaryTopics":[],"typicalErrors":[{"error":"E50001","correct":"total <- computeSum(10, 20)","incorrect":"computeSum(10, 20)","explanation":"Removing the variable declaration means later references to the variable become unresolved, triggering E50001. See ek9 -h E50001 for details."},{"error":"E50001","correct":"priceStr <- formatPrice(9.99)","incorrect":"formatPrice(9.99)","explanation":"Removing the variable declaration means later references to the variable become unresolved, triggering E50001. See ek9 -h E50001 for details."}],"companions":[]}
{"id":642,"category":"Generics","question":"How does constructor-based type inference work for generic classes?","url":"https://ek9.io/qa/QA0642.html","alternatePhrasings":["How does EK9 infer generic type parameters from constructor arguments?","What is E06030 generic constructor inappropriate?","Why must constructor parameter count match type parameter count for inference?"],"answer":"EK9 infers generic type parameters from constructor arguments. For inference to work, the number of constructor parameters must match the number of type parameters, and each constructor parameter type must correspond to the type parameter in the same position.\n\nINFERRENCE RULE\nA generic class 'Box of type T' with constructor 'Box(val as T)' allows inference:\n  box <- Box(42)\nThe compiler sees Integer argument, infers T = Integer.\n\nMATCHING REQUIREMENT\nFor multi-parameter generics, parameter count must match:\n  Pair of type (A, B)\n    Pair(a as A, b as B)\nNow 'Pair(\"hello\", 42)' infers A=String, B=Integer.\n\nEXPLICIT ALTERNATIVE\nWhen inference cannot work, specify types explicitly:\n  container <- Container() of String\n\nSee Q194 for generic class basics. See Q196 for multi-parameter generics. See Q643 for two-constructor requirement. See Q644 for constructor parameter type matching.\nSee Q681 for generic parameterization syntax. See Q682 for generic encapsulation. See Q683 for generic constructor rules.","ek9Example":"defines module qa.genericsdeep.constructorinference\n\n  defines class\n\n    <?-\n      Single type parameter with matching constructor.\n      Enables type inference from constructor argument.\n    -?>\n    Box of type T\n      content as T?\n\n      Box() as pure\n        content :=? T()\n\n      Box() as pure\n        -> initialValue as T\n        content :=? initialValue\n\n      getContent()\n        <- rtn as T: T()\n        rtn :=? T(content)\n\n      default operator ?\n\n    <?-\n      Multi-parameter generic with matching constructor.\n      Two type params, two constructor params, in order.\n    -?>\n    Mapping of type (K, V)\n      theKey as K?\n      theVal as V?\n\n      Mapping() as pure\n        theKey :=? K()\n        theVal :=? V()\n\n      Mapping() as pure\n        ->\n          k as K\n          v as V\n        theKey :=? k\n        theVal :=? v\n\n      key()\n        <- rtn as K: K()\n        rtn :=? K(theKey)\n\n      val()\n        <- rtn as V: V()\n        rtn :=? V(theVal)\n\n      default operator ?\n\n  defines program\n\n    GenericConstructorInferenceDemo()\n      stdout <- Stdout()\n\n      // === INFERENCE FROM SINGLE ARGUMENT ===\n\n      intBox <- Box(42)\n      stdout.println(`Integer box set: ${intBox?}`)\n\n      strBox <- Box(\"hello\")\n      stdout.println(`String box set: ${strBox?}`)\n\n      // === INFERENCE FROM MULTIPLE ARGUMENTS ===\n\n      entry <- Mapping(\"name\", 42)\n      stdout.println(`Mapping set: ${entry?}`)\n\n      // === EXPLICIT WHEN NO ARGS ===\n\n      emptyBox <- Box() of String\n      stdout.println(`Empty box set: ${emptyBox?}`)","migrationContext":"Java: type inference via diamond operator (<>) and constructor args. Rust: turbofish syntax (::<Type>) when inference fails. Go: type inference from function arguments (Go 1.18+). Kotlin: inferred from constructor args or explicit <Type>. EK9: inference from constructor args when param count matches type param count, explicit 'of Type' otherwise.","keywords":["E06030","class","constant","constructor","generic","inference","instantiation","match","parameter","type","type-parameter"],"primaryTopics":[],"typicalErrors":[{"error":"E06030","correct":"    Box of type T\n      content as T?\n\n      Box() as pure\n        content :=? T()\n\n      Box() as pure","incorrect":"Box of type T\n      content as T?\n\n      default Box() as pure\n\n      Box() as pure\n        ->\n          initialValue as T\n          extra as Integer","explanation":"Generic constructor type inference requires the number of constructor parameters to match the number of type parameters. Box has one type parameter T, so the inferred constructor must have exactly one parameter of type T. Adding extra parameters breaks inference. See ek9 -h E06030 for details."}],"companions":[]}
{"id":643,"category":"Generics","question":"Why must a generic class have both a default and an inferred-type constructor?","url":"https://ek9.io/qa/QA0643.html","alternatePhrasings":["What is E06040 generic type requires two constructors?","Why do I need two constructors for a generic class in EK9?","What constructors does a generic type need in EK9?"],"answer":"EK9 requires generic types to have exactly two constructors: a default (no-argument) constructor and an inferred-type constructor whose parameters match the type parameters.\n\nTWO CONSTRUCTOR RULE\nGeneric types need:\n1. A default constructor: 'default MyType()' for creating unset instances\n2. An inferred constructor: 'MyType(val as T)' for type-inferred creation\n\nWHY BOTH ARE NEEDED\nThe default constructor enables:\n  container <- Container() of String\nCreates an instance when no initial value is available.\n\nThe inferred constructor enables:\n  container <- Container(\"hello\")\nType parameter inferred from the argument.\n\nDEFAULT KEYWORD\nThe 'default' keyword generates a no-argument constructor:\n  default Container() as pure\nThis creates a Container where T is unset.\n\nSee Q642 for constructor inference. See Q644 for constructor argument type matching. See Q194 for generic class basics.","ek9Example":"defines module qa.genericsdeep.twoconstructors\n\n  defines class\n\n    <?-\n      Correct: generic class with both default and inferred constructors.\n      Default constructor creates unset instance.\n      Inferred constructor enables type inference.\n    -?>\n    Holder of type T\n      stored as T?\n\n      Holder() as pure\n        stored :=? T()\n\n      Holder() as pure\n        -> initial as T\n        stored :=? initial\n\n      retrieve()\n        <- rtn as T: T()\n        rtn :=? T(stored)\n\n      default operator ?\n\n    <?-\n      Another correct example with multi-parameter generic.\n      Both default and inferred constructors present.\n    -?>\n    KeyVal of type (K, V)\n      theKey as K?\n      theValue as V?\n\n      KeyVal() as pure\n        theKey :=? K()\n        theValue :=? V()\n\n      KeyVal() as pure\n        ->\n          k as K\n          v as V\n        theKey :=? k\n        theValue :=? v\n\n      default operator ?\n\n  defines program\n\n    TwoConstructorDemo()\n      stdout <- Stdout()\n\n      // === DEFAULT CONSTRUCTION (needs explicit type) ===\n\n      emptyHolder <- Holder() of Integer\n      stdout.println(`Empty holder set: ${emptyHolder?}`)\n\n      // === INFERRED CONSTRUCTION ===\n\n      filledHolder <- Holder(\"data\")\n      stdout.println(`Filled holder set: ${filledHolder?}`)\n\n      // === MULTI-PARAM DEFAULT ===\n\n      emptyKV <- KeyVal() of (String, Float)\n      stdout.println(`Empty KV set: ${emptyKV?}`)\n\n      // === MULTI-PARAM INFERRED ===\n\n      filledKV <- KeyVal(\"price\", 9.99)\n      stdout.println(`Filled KV set: ${filledKV?}`)","migrationContext":"Java: no constructor requirements for generics, relies on 'new T()' (impossible due to erasure). C++: default-constructible via 'requires std::default_initializable<T>'. Rust: Default trait optional. Go: zero-value initialization built in. Kotlin: no mandatory constructor count. EK9: two constructors required, default + inferred, enabling both patterns.","keywords":["E06040","class","constant","constructor","default","generic","inferred","instantiation","requirement","two","type-parameter"],"primaryTopics":[],"typicalErrors":[{"error":"E06040","correct":"      Holder() as pure\n        stored :=? T()\n\n      Holder() as pure\n        -> initial as T","incorrect":"      Holder() as pure\n        -> initial as T","explanation":"A generic type requires both a default (no-argument) constructor and an inferred-type constructor; deleting the no-arg constructor leaves only the inferred one and fails. See ek9 -h E06040 for details."}],"companions":[]}
{"id":644,"category":"Generics","question":"How must constructor argument types match generic type parameters?","url":"https://ek9.io/qa/QA0644.html","alternatePhrasings":["What is E06050 constructor argument must match parametric types?","Why must constructor parameter order match type parameter order?","How does EK9 validate generic constructor signatures?"],"answer":"When a generic type has an inferred constructor, the constructor parameter types must match the type parameters in both type and order. 'MyType of type (A, B)' must have constructor 'MyType(a as A, b as B)' with A first and B second.\n\nORDER MATTERS\nFor 'Pair of type (A, B)', the constructor must be:\n  Pair(first as A, second as B)\nNot 'Pair(first as B, second as A)' which reverses the types.\n\nTYPE MATCHING\nEach constructor parameter must use the corresponding type parameter:\n  Position of type T\n    Position(x as T)  // T matches the type parameter\n\nWHY THIS RESTRICTION\nThe compiler uses the constructor to infer type arguments. If 'Pair(\"hello\", 42)' is written, the compiler maps argument 1 to A (String) and argument 2 to B (Integer). The positional correspondence must be unambiguous.\n\nSee Q642 for constructor inference. See Q643 for two-constructor requirement. See Q645 for public constructor requirement.","ek9Example":"defines module qa.genericsdeep.constructorargtypes\n\n  defines class\n\n    <?-\n      Single-param: constructor argument type matches type parameter T.\n    -?>\n    Wrapper of type T\n      wrapped as T?\n\n      Wrapper() as pure\n        wrapped :=? T()\n\n      Wrapper() as pure\n        -> item as T\n        wrapped :=? item\n\n      unwrap()\n        <- rtn as T: T()\n        rtn :=? T(wrapped)\n\n      default operator ?\n\n    <?-\n      Multi-param: constructor argument order matches type parameter order.\n      (A, B) maps to (first, second) positionally.\n    -?>\n    Entry of type (A, B)\n      first as A?\n      second as B?\n\n      Entry() as pure\n        first :=? A()\n        second :=? B()\n\n      Entry() as pure\n        ->\n          a as A\n          b as B\n        first :=? a\n        second :=? b\n\n      getFirst()\n        <- rtn as A: A()\n        rtn :=? A(first)\n\n      getSecond()\n        <- rtn as B: B()\n        rtn :=? B(second)\n\n      default operator ?\n\n  defines program\n\n    ConstructorArgTypeDemo()\n      stdout <- Stdout()\n\n      // === SINGLE PARAM INFERENCE ===\n\n      intWrap <- Wrapper(100)\n      stdout.println(`Int wrapper: ${intWrap?}`)\n\n      strWrap <- Wrapper(\"text\")\n      stdout.println(`String wrapper: ${strWrap?}`)\n\n      // === MULTI PARAM INFERENCE (positional) ===\n\n      record <- Entry(\"id-001\", 42)\n      stdout.println(`Entry set: ${record?}`)\n\n      first <- record.getFirst()\n      if first?\n        stdout.println(`First: ${first}`)","migrationContext":"Java: no constructor-to-type-param mapping (erasure). C++: template argument deduction from constructor (C++17 CTAD). Rust: no constructor syntax, uses 'new' pattern. Go: no constructor, zero-value + explicit init. Kotlin: constructor infers from usage like Java. EK9: strict positional mapping between type params and constructor params.","keywords":["E06050","argument","constant","constructor","generic","inference","match","order","positional","type","type-parameter"],"primaryTopics":[],"typicalErrors":[{"error":"E06050","correct":"    Entry of type (A, B)\n      first as A?\n      second as B?\n\n      Entry() as pure\n        first :=? A()\n        second :=? B()\n\n      Entry() as pure\n        ->","incorrect":"Entry of type (A, B)\n      first as A?\n      second as B?\n\n      default Entry() as pure\n\n      Entry() as pure\n        ->\n          a as B\n          b as A","explanation":"Constructor parameter types must match the type parameters in the same positional order. For 'Entry of type (A, B)', the first constructor parameter must be type A and the second must be type B. Reversing them breaks the positional correspondence the compiler uses for type inference. See ek9 -h E06050 for details."}],"companions":[]}
{"id":645,"category":"Generics","question":"Can a generic type constructor be private?","url":"https://ek9.io/qa/QA0645.html","alternatePhrasings":["What is E06060 generic constructors must be public?","Can I use private constructors in a generic class?","When is a private constructor allowed on a generic type?"],"answer":"EK9 generic type constructors are public, with ONE exception: the no-argument DEFAULT constructor may be declared 'default private'. This is the escape hatch for a generic that has an uninitialised conceptual-T property it cannot give an unset value to - privacy then stops external code creating a partially-constructed (null-field) instance, while the inferred constructor still supplies a real value.\n\nTHE PUBLIC RULE\nThe inferred (parameterised) constructor must be public so the compiler can infer type arguments and instantiate from any context:\n  Container of type T\n    item as T?\n    Container() as pure\n      item :=? T()                  // give the optional field an unset T - never null\n    Container(val as T) as pure       // inferred constructor: must be public\n      item :=? val\n\nTHE PRIVATE NO-ARG ESCAPE HATCH\nWhen a generic cannot give its field an unset 'T()' (T may be abstract, may hold a function, or may lack a public no-arg constructor), declare the default private:\n  default private Container() as pure\nExternal code then cannot create an empty Container; it must pass a value to the inferred constructor.\n\nSTILL NOT ALLOWED\n1. A private or protected PARAMETERISED constructor - type inference needs it public.\n2. A protected no-arg default - it would still allow subclass uninitialised construction.\n\nSee Q642 for constructor inference. See Q643 for two-constructor requirement. See Q194 for generic class basics.\nSee Q682 for generic encapsulation. See Q683 for generic constructor rules.","ek9Example":"defines module qa.genericsdeep.publicconstructor\n\n  defines class\n\n    <?-\n      Correct: both constructors are public (default access).\n      Generic types require public constructors for inference.\n    -?>\n    Cache of type T\n      stored as T?\n\n      Cache() as pure\n        stored :=? T()\n\n      Cache() as pure\n        -> initial as T\n        stored :=? initial\n\n      retrieve()\n        <- rtn as T: T()\n        rtn :=? T(stored)\n\n      default operator ?\n\n    <?-\n      Correct: using 'default' keyword generates public constructor.\n    -?>\n    Slot of type T\n      item as T?\n\n      Slot() as pure\n        item :=? T()\n\n      Slot() as pure\n        -> initialValue as T\n        item :=? initialValue\n\n      default operator ?\n\n  defines program\n\n    PublicConstructorDemo()\n      stdout <- Stdout()\n\n      // === PUBLIC CONSTRUCTORS ENABLE INFERENCE ===\n\n      intCache <- Cache(42)\n      stdout.println(`Cache set: ${intCache?}`)\n\n      strSlot <- Slot(\"data\")\n      stdout.println(`Slot set: ${strSlot?}`)\n\n      // === PUBLIC DEFAULT ENABLES EXPLICIT TYPING ===\n\n      emptyCache <- Cache() of Float\n      stdout.println(`Empty cache set: ${emptyCache?}`)","migrationContext":"Java: generic classes can have private constructors (factory pattern). C++: templates can have private constructors. Rust: no constructor keyword, but 'new' can be private. Go: unexported functions serve as private constructors. EK9: generic constructors must be public for type inference and external instantiation.","keywords":["E06060","access","constant","constructor","generic","instantiation","private","public","type-parameter","visibility"],"primaryTopics":[],"typicalErrors":[{"error":"E06060","correct":"Cache() as pure\n        -> initial as T\n        stored :=? initial","incorrect":"private Cache() as pure\n        -> initial as T\n        stored :=? initial","explanation":"Generic type constructors must be public. A private constructor would prevent type inference and instantiation from external code. The compiler needs unrestricted access to constructors for generic type instantiation. See ek9 -h E06060 for details."}],"companions":[]}
{"id":646,"category":"Generics","question":"Where is type inference not supported in generic type and function bodies?","url":"https://ek9.io/qa/QA0646.html","alternatePhrasings":["What is E06070 type inference not supported in generic context?","When must I use explicit types inside generic definitions?","Why cannot I use type inference inside a generic class body?"],"answer":"Inside a generic type or function body, EK9 does not support type inference for local variables when the type would depend on the generic parameter. You must use explicit type declarations.\n\nINFERENCE LIMITATION\nInside a generic definition, the compiler cannot infer types that depend on T because T is not yet known. Explicit typing is required when creating instances of T or using T-typed expressions.\n\nEXPLICIT RETURN TYPES\nGeneric functions must declare return types explicitly:\n  transform() of type (S, T) as open\n    -> source as S\n    <- target as T: T()\n\nFIELD DECLARATIONS\nFields in generic classes must use the type parameter explicitly:\n  Container of type T\n    item as T?\n\nWHEN INFERENCE WORKS\nType inference works at the CALL SITE, not inside the generic body:\n  intContainer <- Container(42)  // inference works here\nBut inside Container's methods, T must be used explicitly.\n\nSee Q642 for constructor inference at call site. See Q648 for function constraints. See Q194 for generic class basics.","ek9Example":"defines module qa.genericsdeep.inferencelimits\n\n  defines function\n\n    <?-\n      Correct: explicit types used throughout the generic body.\n      Return type explicitly declared as T.\n    -?>\n    identity() of type T as open\n      -> item as T\n      <- rtn as T: T(item)\n\n    <?-\n      Correct: multi-param generic with explicit return type.\n      Both S and T used with explicit type annotations.\n    -?>\n    converter() of type (S, T) as open\n      -> source as S\n      <- target as T: T()\n      require source?\n\n  defines class\n\n    <?-\n      Correct: all fields and method return types use T explicitly.\n      No reliance on type inference inside the generic body.\n    -?>\n    Stack of type T\n      items as List of T?\n\n      Stack()\n        items :=? List() of T\n\n      Stack()\n        -> initial as T\n        items: List() of T\n        items += initial\n\n      push()\n        -> item as T\n        if not items?\n          items: List() of T\n        items += item\n\n      count() as pure\n        <- rtn as Integer: 0\n        if items?\n          rtn: length items\n\n      default operator ?\n\n  defines program\n\n    InferenceLimitsDemo()\n      stdout <- Stdout()\n\n      // === INFERENCE AT CALL SITE (works) ===\n\n      result <- identity(42)\n      stdout.println(`Identity: ${result}`)\n\n      // === EXPLICIT TYPE AT CALL SITE ===\n\n      stack <- Stack(\"first\")\n      stack.push(\"second\")\n      stack.push(\"third\")\n      stdout.println(`Stack count: ${stack.count()}`)","migrationContext":"Java: type inference works inside generics (var keyword, diamond). C++: auto deduction inside templates. Rust: type inference inside generic functions with compiler assistance. Go: type inference limited inside generics too. Kotlin: type inference works inside generic bodies. EK9: no inference inside generic bodies, explicit T usage required.","keywords":["E06070","body","context","explicit","function","generic","inference","limitation","parameter","type","type-parameter"],"primaryTopics":[],"typicalErrors":[{"error":"E50060","correct":"stdout.println(`Stack count: ${stack.count()}`)","incorrect":"stdout.println(stack.count().toString())","explanation":"Integer has no toString() method in EK9. Use the $ prefix operator or string interpolation. See ek9 -h E50060 for details."}],"companions":[]}
{"id":647,"category":"Generics","question":"Why cannot I use a function type as a generic constraint?","url":"https://ek9.io/qa/QA0647.html","alternatePhrasings":["What is E06080 constrained functions not supported?","Can I constrain a generic type parameter to a function type?","Why does EK9 prohibit function types in constrain by?"],"answer":"EK9 does not allow function types as constraints in 'constrain by'. Only class types and traits can be used as constraints. Function types have a different dispatch model and cannot serve as type bounds.\n\nPROHIBITED PATTERN\nYou cannot write:\n  Handler of type T constrain by SomeFunction  // ERROR E06080\nFunction types cannot be used with 'constrain by'.\n\nWHY PROHIBITED\nFunction types in EK9 define signatures (input types and return type). They are not class hierarchies with methods. Constraining T to a function would mean T must be callable, but EK9's generic system works with method access, not call-ability.\n\nCORRECT ALTERNATIVES\nUse a trait or abstract class as the constraint:\n  Handler of type T constrain by Processable\nDefine a trait with the methods you need, then constrain by that trait.\n\nFUNCTION PARAMETERS\nIf you need to pass functions to generics, use function-typed parameters:\n  process()\n    -> mapper as Mapper\nWhere Mapper is a defined function type, passed as a parameter rather than a constraint.\n\nSee Q195 for constrain by with classes. See Q646 for inference limits. See Q649 for generic function implementation.","ek9Example":"defines module qa.genericsdeep.nofunctionconstraint\n\n  defines trait\n\n    <?-\n      Correct approach: define a trait with the methods you need.\n      Then constrain by the trait, not by a function type.\n    -?>\n    Describable\n      description() as pure abstract\n        <- rtn as String?\n\n  defines class\n\n    Planet with trait of Describable\n      planetName as String?\n\n      default private Planet() as pure\n\n      Planet() as pure\n        -> n as String\n        planetName :=? String(n)\n\n      override description() as pure\n        <- rtn as String: String(planetName)\n\n      override operator ? as pure\n        <- rtn as Boolean: planetName?\n\n    Star with trait of Describable\n      starName as String?\n\n      default private Star() as pure\n\n      Star() as pure\n        -> n as String\n        starName :=? String(n)\n\n      override description() as pure\n        <- rtn as String: String(starName)\n\n      override operator ? as pure\n        <- rtn as Boolean: starName?\n\n  defines function\n\n    <?-\n      A function type - cannot be used as a constraint.\n    -?>\n    CheckFunction()\n      -> arg0 as String\n      <- rtn as Boolean: arg0?\n\n    <?-\n      Correct: constrain by a trait, not a function.\n      The trait provides the methods needed inside the generic.\n    -?>\n    describeItem() of type T constrain by Describable as open\n      -> item as T\n      <- rtn as String: item.description()\n\n  defines program\n\n    NoFunctionConstraintDemo()\n      stdout <- Stdout()\n\n      planet <- Planet(\"Mars\")\n      planetDesc <- describeItem(planet)\n      stdout.println(`Planet: ${planetDesc}`)\n\n      star <- Star(\"Polaris\")\n      starDesc <- describeItem(star)\n      stdout.println(`Star: ${starDesc}`)","migrationContext":"Java: can use functional interfaces as bounds '<T extends Function<S,R>>'. C++: concepts can constrain to callable. Rust: trait bounds with Fn/FnMut/FnOnce. Go: interfaces can include method signatures (callable). Kotlin: can bound by function types. EK9: function types excluded from 'constrain by', use traits instead.","keywords":["E06080","bound","constant","constrain","constraint","function","generic","prohibited","trait","type-parameter"],"primaryTopics":[],"typicalErrors":[{"error":"E50010","correct":"constrain by Describable as open","incorrect":"constrain by NoSuchTrait as open","explanation":"The constraining type must be a resolvable class or trait. Using a name that does not exist triggers a type-not-resolved error. See ek9 -h E50010 for details."},{"error":"E06080","correct":"    describeItem() of type T constrain by Describable as open","incorrect":"    describeItem() of type T constrain by CheckFunction as open","explanation":"Generic types cannot be constrained by function types. Use a trait or class as the constraint. See ek9 -h E06080 for details."}],"companions":[]}
{"id":648,"category":"Generics","question":"Why cannot I use named dynamic classes inside a generic type definition?","url":"https://ek9.io/qa/QA0648.html","alternatePhrasings":["What is E06090 named dynamic class in generic?","Can I create dynamic classes inside a generic class body?","Why are named dynamic classes prohibited in generic definitions?"],"answer":"EK9 does not allow named dynamic classes inside generic type or function definitions. Dynamic classes created inside generics must be anonymous (unnamed).\n\nPROHIBITED PATTERN\nInside a generic body, you cannot define a named dynamic class because the name would need to be monomorphised for each type instantiation, creating ambiguous class identities.\n\nCORRECT PATTERN\nUse anonymous dynamic classes with traits:\n  handler <- () with trait of EventHandler as class\n    override handle()\n      <- rtn as Boolean: true\nNo name means no monomorphisation conflict.\n\nALTERNATIVE: DEFINE OUTSIDE\nDefine the class outside the generic, then use it inside:\n  ConcreteHandler with trait of EventHandler\n    ...\nThen reference ConcreteHandler from within the generic body.\n\nSee Q647 for function constraint limits. See Q646 for type inference limits. See Q194 for generic class basics.","ek9Example":"defines module qa.genericsdeep.nonameddynamic\n\n  defines trait\n\n    <?-\n      Trait used as contract for dynamic classes.\n    -?>\n    Renderable\n      render() as pure abstract\n        <- rtn as String?\n\n  defines class\n\n    <?-\n      Named class defined OUTSIDE the generic (correct approach).\n      Can be referenced from inside generic bodies.\n    -?>\n    TextRenderer with trait of Renderable\n      content as String?\n\n      default private TextRenderer() as pure\n\n      TextRenderer() as pure\n        -> txt as String\n        content :=? String(txt)\n\n      override render() as pure\n        <- rtn as String: String(content)\n\n      override operator ? as pure\n        <- rtn as Boolean: content?\n\n    <?-\n      Generic class that uses the trait, not named dynamic classes.\n    -?>\n    Displayer of type T constrain by Renderable\n      item as T?\n\n      default private Displayer() as pure\n\n      Displayer() as pure\n        -> inputItem as T\n        item :=? inputItem\n\n      show()\n        <- rtn as String: \"empty\"\n        if item?\n          rtn: item.render()\n\n      default operator ?\n\n  defines program\n\n    NoDynamicInGenericDemo()\n      stdout <- Stdout()\n\n      renderer <- TextRenderer(\"EK9 generics\")\n      displayer <- Displayer(renderer)\n      output <- displayer.show()\n      stdout.println(`Display: ${output}`)","migrationContext":"Java: anonymous inner classes freely used inside generics. C++: lambdas and local classes inside templates. Rust: closures inside generic functions. Go: anonymous structs (limited). Kotlin: anonymous objects inside generics. EK9: named dynamic classes prohibited in generic bodies, use anonymous or define outside.","keywords":["E06090","anonymous","capture","class","closure","dynamic","generic","monomorphisation","named","prohibited","type-parameter"],"primaryTopics":[],"typicalErrors":[{"error":"E06090","correct":"if item?\n          rtn: item.render()","incorrect":"named as Renderable: MyRenderer() with trait of Renderable as class\n          override render()\n            <- rtn as String: \"dynamic\"","explanation":"Named dynamic classes like 'MyRenderer' are not allowed inside generic type definitions. The generic body is monomorphised for each type parameter, so a named class would be created multiple times. Use anonymous dynamic classes instead. See ek9 -h E06090 for details."}],"companions":[]}
{"id":649,"category":"Generics","question":"Why must generic functions provide an implementation?","url":"https://ek9.io/qa/QA0649.html","alternatePhrasings":["What is E06100 generic function implementation required?","Can I declare a generic function without a body?","How do generic function templates work in EK9?"],"answer":"EK9 requires generic functions to provide an implementation. Unlike abstract methods in classes, generic functions are templates that must contain code to execute when instantiated.\n\nIMPLEMENTATION REQUIRED\nA generic function must have a body:\n  transform() of type T as open\n    -> item as T\n    <- rtn as T: T(item)\nThe body provides the default behavior.\n\nAS OPEN FOR EXTENSION\nMark a generic function 'as open' to allow specialised implementations:\n  compare() of type T as open\n    -> a as T, b as T\n    <- rtn as Integer: a <=> b\nConcreted implementations can override this default.\n\nDYNAMIC INSTANTIATION\nGeneric functions create concrete instances:\n  intCompare <- () is compare of Integer as function\nThis uses the template's default implementation.\n\nCUSTOM IMPLEMENTATION\nDynamic functions can provide custom bodies:\n  customCompare <- () is compare of Integer as function\n    rtn: b <=> a  // reversed\n\nSee Q197 for extending generics. See Q647 for function constraint rules. See Q194 for generic class basics.","ek9Example":"defines module qa.genericsdeep.functionimplementation\n\n  defines function\n\n    <?-\n      Correct: generic function with implementation body.\n      'as open' allows extension when parameterised.\n    -?>\n    doubleValue() of type T as open\n      -> item as T\n      <- rtn as T: item + item\n\n    <?-\n      Correct: multi-param generic function with body.\n    -?>\n    selectFirst() of type (A, B) as open\n      ->\n        first as A\n        second as B\n      <- rtn as A: A(first)\n      require second?\n\n    <?-\n      Correct: constrained generic function with implementation.\n    -?>\n    stringify() of type T as open\n      -> item as T\n      <- rtn as String: $item\n\n  defines program\n\n    GenericFunctionImplDemo()\n      stdout <- Stdout()\n\n      // === USING DEFAULT IMPLEMENTATIONS ===\n\n      doubled <- doubleValue(21)\n      stdout.println(`Doubled: ${doubled}`)\n\n      strDoubled <- doubleValue(\"ab\")\n      stdout.println(`String doubled: ${strDoubled}`)\n\n      // === MULTI-PARAM GENERIC FUNCTION ===\n\n      selected <- selectFirst(\"chosen\", 99)\n      stdout.println(`Selected: ${selected}`)\n\n      // === STRINGIFY GENERIC ===\n\n      asStr <- stringify(3.14)\n      stdout.println(`Stringified: ${asStr}`)","migrationContext":"Java: abstract methods exist but generic methods always have bodies. C++: templates always have bodies (no separate declaration for function templates). Rust: generic functions always have bodies. Go: generic functions always have bodies. Kotlin: generic functions always have bodies. EK9: same requirement, enforced with E06100 if body missing.","keywords":["E06100","body","function","generic","implementation","open","required","template","type-parameter"],"primaryTopics":[],"typicalErrors":[{"error":"E07110","correct":"doubleValue() of type T as open\n      -> item as T\n      <- rtn as T: item + item","incorrect":"doubleValue() of type T as open\n      -> item as T\n      <- rtn as T?","explanation":"Generic functions must provide an implementation body. Unlike abstract methods in classes, generic functions are templates that generate concrete code when instantiated with specific types. A generic function without a body has nothing to instantiate. See ek9 -h E07110 for details."}],"companions":[]}
{"id":650,"category":"Generics","question":"What operators can be used with function-typed type parameters in generics?","url":"https://ek9.io/qa/QA0650.html","alternatePhrasings":["What is E06120 function used in generic operator restriction?","Why can I only use the ? operator on function type parameters?","What limitations exist for function types in generic contexts?"],"answer":"When a generic type parameter happens to be filled with a function type, only the '?' (isSet) operator is supported. Other operators like '+', '-', ':=:', ':^:', ':~:' are not applicable to function types.\n\nONLY QUESTION MARK\nFunction types support only '?' to check if they are set:\n  if callback?\n    // function is assigned and ready to call\n\nWHY LIMITED\nFunction types represent callable code, not data values. Arithmetic operators (+, -, *) make no sense on functions. Copy/merge/replace operators (:=:, :~:, :^:) would have unclear semantics for closures with captured state.\n\nCORRECT USAGE\nCheck if a function parameter is set, then call it:\n  if handler?\n    result <- handler(inputData)\n\nDESIGN PRINCIPLE\nFunctions are called, not manipulated. EK9 treats functions as first-class values for passing and storing, but arithmetic and mutation operators are reserved for data types.\n\nSee Q647 for function constraint prohibition. See Q649 for generic function implementation. See Q58 for generic functions.","ek9Example":"defines module qa.genericsdeep.functioningeneric\n\n  defines function\n\n    <?-\n      A simple function type that can be used as a type parameter.\n    -?>\n    formatter() as open\n      -> item as String\n      <- rtn as String: item\n\n  defines class\n\n    <?-\n      Generic class that stores a value of type T.\n      When T is a function type, only ? operator works.\n      This example uses T with standard types, showing\n      the ? operator that works for all types including functions.\n    -?>\n    Holder of type T\n      stored as T?\n\n      //A generic that may hold a FUNCTION cannot give its field an unset 'T()' (you cannot construct a\n      //function - E06110), so the no-arg default is 'default private': you cannot externally create an\n      //empty one, you must use the inferred constructor with a value.\n      default private Holder() as pure\n\n      Holder() as pure\n        -> initialItem as T\n        stored :=? initialItem\n\n      isPresent() as pure\n        <- rtn as Boolean: stored?\n\n      default operator ?\n\n  defines program\n\n    FunctionInGenericDemo()\n      stdout <- Stdout()\n\n      // === STANDARD TYPE: all operators work ===\n\n      intHolder <- Holder(42)\n      stdout.println(`Int holder present: ${intHolder.isPresent()}`)\n\n      strHolder <- Holder(\"hello\")\n      stdout.println(`String holder present: ${strHolder.isPresent()}`)\n\n      // === FUNCTION TYPE: only ? works ===\n\n      upperFormatter <- () is formatter as function\n        rtn: item.upperCase()\n\n      funcHolder <- Holder(upperFormatter)\n      stdout.println(`Function holder present: ${funcHolder.isPresent()}`)\n\n      // === LIMIT: a function-holding generic cannot be empty-constructed ===\n      // 'Holder() of Integer' is NOT available: the no-arg default is private because 'T()' is unavailable\n      // for a generic that may hold a function. Construct with a value via the inferred constructor instead.","migrationContext":"Java: function interfaces support only apply/invoke. C++: std::function supports only call operator. Rust: Fn traits define only call semantics. Go: functions only support calling. Kotlin: function types support only invoke. EK9: function type parameters in generics limited to '?' operator only.","keywords":["E06120","callable","function","generic","isSet","limitation","operator","question","type-parameter"],"primaryTopics":[],"typicalErrors":[{"error":"E50060","correct":"isPresent() as pure\n        <- rtn as Boolean: stored?","incorrect":"combine() as pure\n        -> other as T\n        <- rtn as T: stored + other","explanation":"When a generic type parameter is filled with a function type, only the '?' (isSet) operator is supported. Operators like '+', '-', ':=:', ':^:', ':~:' are not applicable to function types. Functions are called, not manipulated with arithmetic or mutation operators. See ek9 -h E50060 for details."}],"companions":[]}
{"id":651,"category":"Generics","question":"How must constrained type constructors match the parameterising type?","url":"https://ek9.io/qa/QA0651.html","alternatePhrasings":["What is E06130 constrained type constructor missing?","Why must constrained generic subtypes have matching constructors?","How do constructor requirements flow from constraints to subtypes?"],"answer":"When a generic type is constrained with 'constrain by', the constraining type's constructors must be present on any type used to parameterise the generic. If the constraining type has a String constructor, any actual type argument must also have a String constructor.\n\nCONSTRUCTOR MATCHING\nIf Shape has 'Shape(name as String)', then any T used in 'of type T constrain by Shape' must also have a String constructor.\n\nWHY REQUIRED\nGeneric code may create instances of T using constructors from the constraining type. If the constructor does not exist, the code would fail at instantiation.\n\nCORRECT PATTERN\nEnsure subtypes mirror the constructor signatures:\n  Shape as abstract\n    Shape(n as String)\n  Circle is Shape\n    Circle(n as String)  // matches Shape's constructor\n      super(n)\n\nSee Q195 for constrain by basics. See Q642 for constructor inference. See Q647 for function constraint limits.","ek9Example":"defines module qa.genericsdeep.constraintconstructormatch\n\n  defines class\n\n    <?-\n      Abstract base class used as constraint.\n      Has both default and String constructors.\n    -?>\n    Vehicle as abstract\n      vehicleName as String: String()\n\n      default Vehicle() as pure\n\n      Vehicle() as pure\n        -> n as String\n        vehicleName :=? String(n)\n\n      name() as pure\n        <- rtn as String: String(vehicleName)\n\n      default operator ?\n\n    <?-\n      Concrete subtype with matching constructors.\n      Both default and String constructors present (matching Vehicle).\n    -?>\n    Truck is Vehicle\n      payload <- Integer()\n\n      default Truck() as pure\n\n      Truck() as pure\n        -> n as String\n        super(n)\n\n      Truck() as pure\n        ->\n          n as String\n          cap as Integer\n        super(n)\n        payload :=? cap\n\n      override operator ? as pure\n        <- rtn as Boolean: name()?\n\n    <?-\n      Subtype missing String constructor (for E06130 mutation testing).\n    -?>\n    Bicycle is Vehicle\n\n      default Bicycle() as pure\n\n      override operator ? as pure\n        <- rtn as Boolean: name()?\n\n    <?-\n      Another concrete subtype with matching constructors.\n    -?>\n    Sedan is Vehicle\n      doors as Integer: 4\n\n      default Sedan() as pure\n\n      Sedan() as pure\n        -> n as String\n        super(n)\n\n      override operator ? as pure\n        <- rtn as Boolean: name()?\n\n  defines function\n\n    <?-\n      Constrained generic function.\n      T must be a Vehicle, so T has name() method available.\n    -?>\n    describeVehicle() of type T constrain by Vehicle as open\n      -> item as T\n      <- rtn as String: item.name()\n\n  defines program\n\n    ConstraintConstructorDemo()\n      stdout <- Stdout()\n\n      truck <- Truck(\"Big Rig\")\n      truckDesc <- describeVehicle(truck)\n      stdout.println(`Truck: ${truckDesc}`)\n\n      sedan <- Sedan(\"Family Car\")\n      sedanDesc <- describeVehicle(sedan)\n      stdout.println(`Sedan: ${sedanDesc}`)","migrationContext":"Java: no constructor inheritance or matching requirement (erasure). C++: concepts can require constructibility. Rust: trait bounds define method requirements, not constructors. Go: no constructor concept. Kotlin: no constructor matching for generics. EK9: constraining type constructors must exist on parameterising type.","keywords":["E06130","constant","constraint","constructor","generic","match","missing","parameter","subtype","type-parameter"],"primaryTopics":[],"typicalErrors":[{"error":"E50060","correct":"Truck() as pure\n        -> n as String\n        super(n)","incorrect":"Truck() as pure\n        -> cap as Integer\n        payload :=? cap","explanation":"When a generic type is constrained by Vehicle (which has a String constructor), any type used to parameterise it must also have a matching String constructor. Removing the String constructor from Truck while keeping only a non-matching constructor triggers E50060. See ek9 -h E50060 for details."},{"error":"E06130","correct":"sedan <- Sedan(\"Family Car\")\n      sedanDesc <- describeVehicle(sedan)\n      stdout.println(`Sedan: ${sedanDesc}`)","incorrect":"bicycle <- Bicycle()\n      bicycleDesc <- describeVehicle(bicycle)\n      stdout.println(`Bicycle: ${bicycleDesc}`)","explanation":"Bicycle extends Vehicle but is missing the String constructor that Vehicle defines. When describeVehicle (constrained by Vehicle) is parameterised with Bicycle, the compiler detects the missing constructor match and triggers E06130. See ek9 -h E06130 for details."}],"companions":[]}
{"id":652,"category":"Generics","question":"Why must Result be used with two different types?","url":"https://ek9.io/qa/QA0652.html","alternatePhrasings":["What is E06190 Result must have different types?","Can I use Result with the same type for success and error?","How does EK9 prevent Result type ambiguity?"],"answer":"EK9 requires Result to be parameterised with two DIFFERENT types. Using 'Result of (String, String)' is an error because the compiler cannot distinguish success from failure when both have the same type.\n\nDIFFERENT TYPES REQUIRED\nThe ok type and error type must differ:\n  Result of (Integer, String)  // correct: Integer ok, String error\n  Result of (String, String)   // ERROR E06190: same types\n\nWHY REQUIRED\nResult's purpose is to carry EITHER a success value OR an error value. If both are the same type, there is no way to distinguish which state the Result is in. The type system loses its ability to guarantee safety.\n\nCORRECT PATTERNS\nUse distinct types for success and error:\n  Result of (UserRecord, String)     // success: record, error: message\n  Result of (Integer, ErrorCode)     // success: count, error: enum\n  Result of (Float, Exception)       // success: value, error: exception\n\nCREATING RESULTS\nUse the ok or error constructor:\n  okResult <- Result(42, String())   // ok result\n  errResult <- Result(Integer(), \"failed\")  // error result\n\nSee Q48 for Result basics. See Q198 for built-in generics. See Q104 for error handling patterns.","ek9Example":"defines module qa.genericsdeep.resultdifferenttypes\n\n  defines function\n\n    <?-\n      Correct: Result with different ok and error types.\n      Integer for success value, String for error message.\n    -?>\n    safeDivide() as pure\n      ->\n        numerator as Integer\n        denominator as Integer\n      <- rtn as Result of (Integer, String): Result(Integer(), \"not calculated\")\n\n      if denominator == 0\n        rtn: Result(Integer(), \"division by zero\")\n      else\n        rtn: Result(numerator / denominator, String())\n\n    <?-\n      Correct: Result with custom type and String error.\n    -?>\n    parseScore() as pure\n      -> input as String\n      <- rtn as Result of (Float, String): Result(Float(), \"not parsed\")\n\n      if input?\n        rtn: Result(Float(input), String())\n\n  defines program\n\n    ResultDifferentTypesDemo()\n      stdout <- Stdout()\n\n      // === SUCCESSFUL DIVISION ===\n\n      divResult <- safeDivide(numerator: 10, denominator: 3)\n      stdout.println(`Division result set: ${divResult?}`)\n\n      // === DIVISION BY ZERO ===\n\n      zeroResult <- safeDivide(numerator: 10, denominator: 0)\n      stdout.println(`Zero division set: ${zeroResult?}`)\n\n      // === PARSE RESULT ===\n\n      scoreResult <- parseScore(\"95.5\")\n      stdout.println(`Parse result set: ${scoreResult?}`)","migrationContext":"Java: no built-in Result type. Rust: Result<T, E> allows same types (Result<String, String> is valid). Go: multiple return values (value, error). Kotlin: Result<T> wraps only success, uses exceptions for errors. EK9: Result of (OK, ERR) requires OK and ERR to be different types for unambiguous state.","keywords":["E06190","ambiguity","different","error","generic","guard","ok","result","success","type-parameter","types"],"primaryTopics":[],"typicalErrors":[{"error":"E06190","correct":"rtn as Result of (Integer, String): Result(Integer(), \"not calculated\")","incorrect":"rtn as Result of (String, String): Result(String(), \"not calculated\")","explanation":"Result must be parameterised with two different types so the compiler can distinguish success from error. Using the same type for both (e.g., Result of (String, String)) triggers E06190. See ek9 -h E06190 for details."}],"companions":[]}
{"id":653,"category":"Generics","question":"Why is List of Circle not compatible with List of Shape?","url":"https://ek9.io/qa/QA0653.html","alternatePhrasings":["How does type independence work for parameterised types in EK9?","Does EK9 use type erasure like Java?","Can I pass List of Circle where List of Shape is expected?"],"answer":"EK9 creates truly independent types for each generic parameterisation. 'List of Circle' and 'List of Shape' are completely separate types with no relationship, even if Circle extends Shape.\n\nTYPE INDEPENDENCE\nEach parameterisation is a distinct type:\n  List of Circle   // one type\n  List of Shape    // completely different type\n  List of Integer  // yet another different type\n\nNO ERASURE\nUnlike Java where 'List<Circle>' and 'List<Shape>' become the same 'List' at runtime (erasure), EK9 maintains full type identity. They are separate types that happen to have similar structures.\n\nNO COVARIANCE\nYou cannot pass 'List of Circle' where 'List of Shape' is expected. This prevents the type safety violation where adding a Square to a 'List of Shape' that is actually a 'List of Circle'.\n\nMETHOD OVERLOADING\nBecause types are independent, you can overload methods:\n  process(circles as List of Circle)\n  process(shapes as List of Shape)\nJava cannot do this due to erasure.\n\nSee Q198 for built-in generics. See Q195 for constrain by. See Q196 for multi-parameter generics. See Q655 for method overloading with generics. See Q642 for constructor inference.","ek9Example":"defines module qa.genericsdeep.typeindependence\n\n  defines class\n\n    Colour as abstract\n      colourName as String: String()\n\n      default Colour() as pure\n\n      Colour() as pure\n        -> n as String\n        colourName :=? String(n)\n\n      name() as pure\n        <- rtn as String: String(colourName)\n\n      default operator ?\n\n    Red is Colour\n      default Red() as pure\n      Red() as pure\n        -> n as String\n        super(n)\n\n    Blue is Colour\n      default Blue() as pure\n      Blue() as pure\n        -> n as String\n        super(n)\n\n  defines program\n\n    TypeIndependenceDemo()\n      stdout <- Stdout()\n\n      // === SEPARATE LISTS OF DIFFERENT TYPES ===\n\n      reds <- List() of Red\n      reds += Red(\"Crimson\")\n      reds += Red(\"Scarlet\")\n\n      blues <- List() of Blue\n      blues += Blue(\"Navy\")\n\n      // === EACH LIST IS AN INDEPENDENT TYPE ===\n\n      redCount <- length reds\n      stdout.println(`Red count: ${redCount}`)\n\n      blueCount <- length blues\n      stdout.println(`Blue count: ${blueCount}`)\n\n      // === CANNOT MIX: List of Red != List of Blue ===\n      // reds += Blue(\"Teal\")  // ERROR: Blue is not Red\n\n      // === INDEPENDENT CONTAINERS ===\n\n      redOpt <- Optional(Red(\"Vermillion\"))\n      blueOpt <- Optional(Blue(\"Azure\"))\n      stdout.println(`Red optional set: ${redOpt?}`)\n      stdout.println(`Blue optional set: ${blueOpt?}`)\n\n      // Optional of Red and Optional of Blue are different types\n\n      stdout.println(\"Each parameterised type is a separate independent type\")","migrationContext":"Java: type erasure makes List<Circle> and List<Shape> the same at runtime, cannot overload. C++: templates create independent types (full monomorphisation). Rust: generics create independent types. Go: generics create independent types (Go 1.18+). Kotlin: same erasure as Java. EK9: fully independent types, no erasure, safe method overloading.","keywords":["covariance","erasure","generic","independence","list","migrate","overload","parameterised","separate","type","type-parameter"],"primaryTopics":[],"typicalErrors":[{"error":"E50060","correct":"reds <- List() of Red","incorrect":"reds <- List() of Red\n      reds += Blue(\"Navy\")","explanation":"Each generic parameterisation creates a fully independent type. List of Red and List of Blue are completely separate types with no relationship, so adding a Blue to a List of Red is a type error. See ek9 -h E50060 for details."}],"companions":[]}
{"id":654,"category":"Generics","question":"How does EK9 validate the number of type parameters for generics?","url":"https://ek9.io/qa/QA0654.html","alternatePhrasings":["What is E06020 incorrect number of parameters for generic?","What happens if I provide wrong number of type arguments?","How does EK9 check generic parameter counts?"],"answer":"EK9 validates that the number of type arguments matches the number of type parameters declared on a generic type. Providing too few or too many type arguments produces E06020.\n\nSINGLE PARAMETER TYPES\nTypes declared 'of type T' need exactly one type argument:\n  List of String         // correct: 1 param, 1 arg\n  Optional of Integer    // correct: 1 param, 1 arg\n\nMULTI PARAMETER TYPES\nTypes declared 'of type (K, V)' need exactly two:\n  Dict of (String, Integer)  // correct: 2 params, 2 args\n\nMISMATCH DETECTED\nE06020 is raised when counts do not match:\n  Dict of String          // ERROR: needs 2, got 1\n  List of (String, Integer)  // ERROR: needs 1, got 2\n\nPARAMETER SUPPLY\nAlways match the generic declaration:\n  MyType of type T         -> supply 1 type\n  MyType of type (A, B)    -> supply 2 types\n  MyType of type (X, Y, Z) -> supply 3 types\n\nSee Q196 for multi-parameter generics. See Q198 for built-in generics. See Q642 for constructor inference.","ek9Example":"defines module qa.genericsdeep.parametercount\n\n  defines class\n\n    <?-\n      Single parameter generic: needs exactly 1 type argument.\n    -?>\n    Wrapper of type T\n      item as T?\n\n      Wrapper() as pure\n        item :=? T()\n\n      Wrapper() as pure\n        -> inputItem as T\n        item :=? inputItem\n\n      default operator ?\n\n    <?-\n      Two parameter generic: needs exactly 2 type arguments.\n    -?>\n    Association of type (K, V)\n      theKey as K?\n      theValue as V?\n\n      Association() as pure\n        theKey :=? K()\n        theValue :=? V()\n\n      Association() as pure\n        ->\n          k as K\n          v as V\n        theKey :=? k\n        theValue :=? v\n\n      default operator ?\n\n  defines program\n\n    ParameterCountDemo()\n      stdout <- Stdout()\n\n      // === SINGLE PARAM: one type argument ===\n\n      intWrap <- Wrapper(42)\n      stdout.println(`Int wrapper: ${intWrap?}`)\n\n      strWrap <- Wrapper() of String\n      stdout.println(`String wrapper: ${strWrap?}`)\n\n      // === TWO PARAMS: two type arguments ===\n\n      assoc <- Association(\"key\", 100)\n      stdout.println(`Association: ${assoc?}`)\n\n      emptyAssoc <- Association() of (String, Float)\n      stdout.println(`Empty association: ${emptyAssoc?}`)\n\n      // === BUILT-IN GENERICS FOLLOW SAME RULES ===\n\n      names <- List() of String\n      names += \"Alice\"\n      stdout.println(`List count: ${length names}`)\n\n      mapping <- Dict() of (String, Integer)\n      stdout.println(`Dict set: ${mapping?}`)","migrationContext":"Java: compiler checks type argument count against declaration. C++: template parameter count validated. Rust: generic parameter count validated. Go: type parameter count validated (Go 1.18+). Kotlin: same as Java. EK9: E06020 raised for parameter count mismatch, checked early in compilation.","keywords":["E06020","argument","count","generic","mismatch","parameter","type","type-parameter","validation"],"primaryTopics":[],"typicalErrors":[{"error":"E06020","correct":"Association of type (K, V)","incorrect":"Association of type K","explanation":"The number of type arguments must match the number of type parameters declared on the generic. Association declares two parameters (K, V), so providing only one triggers E06020. See ek9 -h E06020 for details."}],"companions":[]}
{"id":655,"category":"Generics","question":"Can I overload methods with different parameterised types in EK9?","url":"https://ek9.io/qa/QA0655.html","alternatePhrasings":["Does EK9 allow method overloading with generics unlike Java?","What is E06140 ambiguous method match?","How do independent generic types enable overloading?"],"answer":"Because EK9 creates independent types for each parameterisation (no erasure), you can overload methods with different parameterised types. This is impossible in Java due to type erasure.\n\nOVERLOADING WORKS\nBecause List of Integer and List of String are distinct types:\n  process(items as List of Integer)\n  process(items as List of String)\nBoth signatures coexist without ambiguity.\n\nWHY JAVA CANNOT\nJava erases generics at runtime: both List<Integer> and List<String> become List. Two methods with the same erased signature conflict.\n\nAMBIGUITY DETECTION (E06140)\nEK9 still detects GENUINE ambiguity. If two overloads match equally well, E06140 is raised. This happens with coercion costs, not with distinct parameterised types.\n\nDISTINCT TYPE ARGUMENTS\nOverloading with different type arguments is always unambiguous:\n  handler(opt as Optional of String)\n  handler(opt as Optional of Integer)\nThe compiler resolves the correct overload from the argument type.\n\nSee Q653 for type independence. See Q196 for multi-parameter generics. See Q194 for generic class basics.","ek9Example":"defines module qa.genericsdeep.methodoverloading\n\n  defines function\n\n    <?-\n      Functions accepting different parameterised types.\n      Each List parameterisation is an independent type,\n      so these are unambiguous overloads (unlike Java with erasure).\n    -?>\n    sumIntegers()\n      -> items as List of Integer\n      <- rtn as Integer: 0\n      for item in items\n        rtn += item\n\n    joinStrings()\n      -> items as List of String\n      <- rtn as String: \"\"\n      for item in items\n        rtn += item\n\n    countIntegers() as pure\n      -> items as List of Integer\n      <- rtn as Integer: length items\n\n    countStrings() as pure\n      -> items as List of String\n      <- rtn as Integer: length items\n\n  defines program\n\n    MethodOverloadingDemo()\n      stdout <- Stdout()\n\n      // === INTEGER LIST ===\n\n      numbers <- List() of Integer\n      numbers += 10\n      numbers += 20\n      numbers += 30\n      intSum <- sumIntegers(numbers)\n      stdout.println(`Integer sum: ${intSum}`)\n\n      // === STRING LIST ===\n\n      words <- List() of String\n      words += \"hello\"\n      words += \" \"\n      words += \"world\"\n      joined <- joinStrings(words)\n      stdout.println(`Joined: ${joined}`)\n\n      // === COUNT FUNCTIONS FOR DIFFERENT LIST TYPES ===\n\n      intCount <- countIntegers(numbers)\n      stdout.println(`Integer count: ${intCount}`)\n\n      strCount <- countStrings(words)\n      stdout.println(`String count: ${strCount}`)","migrationContext":"Java: cannot overload methods with different generic parameterisations (erasure). C++: can overload with different template instantiations. Rust: no method overloading (uses traits instead). Go: no method overloading. Kotlin: same erasure limitation as Java. EK9: full overloading with distinct parameterised types because no erasure.","keywords":["E06140","ambiguity","distinct","erasure","generic","method","migrate","overload","parameterised","type-parameter"],"primaryTopics":[],"typicalErrors":[{"error":"E01030","correct":"sumIntegers()\n      -> items as List of Integer\n      <- rtn as Integer: 0","incorrect":"sumIntegers()\n      -> items as List of Integer\n      <- rtn as Integer: 0\n    sumIntegers()\n      -> items as List of Integer\n      <- rtn as Integer: 0","explanation":"E01030 is raised when two overloads match equally well, creating genuine ambiguity. Declaring two functions with identical signatures (same name, same parameter types) would be ambiguous. EK9 allows overloading with different parameterised types because they are independent. See ek9 -h E01030 for details."}],"companions":[]}
{"id":656,"category":"Generics","question":"How does EK9 validate argument counts when calling generic functions?","url":"https://ek9.io/qa/QA0656.html","alternatePhrasings":["What are E06280 and E06290 too many or too few arguments?","How does EK9 check function call argument counts?","What happens if I pass wrong number of arguments to a generic function?"],"answer":"EK9 validates that the number of arguments passed to a function or method matches the declared parameter count. Too many arguments raises E06280, too few raises E06290.\n\nEXACT MATCH REQUIRED\nA function declared with two parameters:\n  combine(a as String, b as String)\nMust be called with exactly two arguments:\n  combine(\"hello\", \"world\")  // correct\n\nTOO MANY (E06280)\nPassing more arguments than declared:\n  combine(\"a\", \"b\", \"c\")  // ERROR: expects 2, got 3\n\nTOO FEW (E06290)\nPassing fewer arguments than declared:\n  combine(\"a\")  // ERROR: expects 2, got 1\n\nGENERIC FUNCTION CALLS\nSame validation applies to generic function calls:\n  transform(source as S)  // needs exactly 1 argument\n  transform(x, y)         // ERROR: too many\n\nNO OPTIONAL PARAMETERS\nEK9 has no default parameter values (by design). Every declared parameter must receive an argument.\n\nSee Q597 for no default parameters design. See Q642 for constructor inference. See Q654 for type parameter count.","ek9Example":"defines module qa.genericsdeep.argumentcount\n\n  defines function\n\n    <?-\n      Single parameter function. Requires exactly 1 argument.\n    -?>\n    square() as pure\n      -> number as Integer\n      <- rtn as Integer: number * number\n\n    <?-\n      Two parameter function. Requires exactly 2 arguments.\n    -?>\n    multiply() as pure\n      ->\n        factor1 as Integer\n        factor2 as Integer\n      <- rtn as Integer: factor1 * factor2\n\n    <?-\n      Generic single parameter function.\n    -?>\n    echo() of type T as open\n      -> item as T\n      <- rtn as T: T(item)\n\n  defines program\n\n    ArgumentCountDemo()\n      stdout <- Stdout()\n\n      // === SINGLE ARG: exactly 1 ===\n\n      sq <- square(7)\n      stdout.println(`Square of 7: ${sq}`)\n\n      // === TWO ARGS: exactly 2 ===\n\n      product <- multiply(factor1: 6, factor2: 7)\n      stdout.println(`Product: ${product}`)\n\n      // === GENERIC SINGLE ARG ===\n\n      echoed <- echo(42)\n      stdout.println(`Echoed: ${echoed}`)\n\n      strEchoed <- echo(\"hello\")\n      stdout.println(`Str echoed: ${strEchoed}`)","migrationContext":"Java: exact argument count required (no defaults for methods). C++: default parameters allow fewer args. Rust: exact count required (no defaults). Go: exact count required (no defaults or varargs for regular funcs). Kotlin: default parameters allow fewer. Python: default params and *args. EK9: exact count always, no defaults by design (E06280/E06290).","keywords":["E06280","E06290","argument","count","function","generic","too few","too many","type-parameter","validation"],"primaryTopics":[],"typicalErrors":[{"error":"E06270","correct":"sq <- square(7)","incorrect":"sq <- square(7, 3)","explanation":"square() declares exactly one parameter, so passing two arguments triggers E06270 (too many arguments). See ek9 -h E06270 for details."},{"error":"E06270","correct":"product <- multiply(factor1: 6, factor2: 7)","incorrect":"product <- multiply(factor1: 6)","explanation":"multiply() declares two parameters (factor1 and factor2), so passing only one argument triggers E06270 (too few arguments). See ek9 -h E06270 for details."}],"companions":[]}
{"id":657,"category":"Web Services","question":"How does service URI mapping work with :/path syntax?","url":"https://ek9.io/qa/QA0657.html","alternatePhrasings":["How do I define URI paths for EK9 services?","What is the :/path syntax in service definitions?","How do service URIs bind to methods and operators?"],"answer":"EK9 services use ':/path' syntax for URI mapping. The service declaration includes a base path, and each method or operator adds a relative path.\n\nBASE PATH\nDeclare a service with a root URI:\n  Items :/items\nThis binds the service to the '/items' path prefix.\n\nMETHOD PATHS\nNamed methods declare additional path segments:\n  welcome() as GET for :/welcome\nThis creates a GET endpoint at '/items/welcome'.\n\nOPERATOR PATHS WITH PARAMETERS\nOperators use path parameters:\n  operator -= :/{itemId}\nThis creates a DELETE endpoint at '/items/{itemId}'.\n\nPATH PARAMETER BINDING\nPath parameters bind to method parameters:\n  -> itemId as String\nThe server extracts the value from the URL and passes it.\n\nSee Q199 for GET endpoints. See Q200 for CRUD operators. See Q658 for path parameter binding. See Q202 for parameter binding.\nSee Q684 for service URI paths. See Q685 for service method bodies.","ek9Example":"defines module qa.webdeep.urimapping\n\n  defines service\n\n    <?-\n      Service with base URI and multiple endpoint paths.\n      Each method and operator adds a relative path.\n    -?>\n    Catalogue :/catalogue open\n\n      // GET /catalogue/all — list items\n      listAll() as GET for :/all\n        <- response as HTTPResponse: () with trait HTTPResponse\n          override content()\n            <- rtn as String: `[\"itemA\", \"itemB\"]`\n          override status() as pure\n            <- rtn as Integer: 200\n          override contentType() as pure\n            <- rtn as String: \"application/json\"\n          override cacheControl() as pure\n            <- rtn as String: \"max-age=60\"\n          override contentLanguage() as pure\n            <- rtn as String: \"en\"\n          default operator ?\n\n      // GET /catalogue/{itemCode} — get single item\n      byCode() as GET for :/{itemCode}\n        -> itemCode as String\n        <- response as HTTPResponse: (capturedCode: itemCode) with trait HTTPResponse\n          override content()\n            <- rtn as String: `{\"code\": \"${capturedCode}\"}`\n          override status() as pure\n            <- rtn as Integer: 200\n          override contentType() as pure\n            <- rtn as String: \"application/json\"\n          override cacheControl() as pure\n            <- rtn as String: \"max-age=30\"\n          override contentLanguage() as pure\n            <- rtn as String: \"en\"\n          default operator ?\n\n  defines application\n\n    CatalogueApp\n      register Catalogue()\n\n  defines program\n\n    ServiceUriMappingDemo()\n      stdout <- Stdout()\n\n      stdout.println(\"Service URI mapping:\")\n      stdout.println(\"  Base: /catalogue\")\n      stdout.println(\"  GET /catalogue/all -> listAll()\")\n      stdout.println(\"  GET /catalogue/{itemCode} -> byCode()\")","migrationContext":"Java: @RequestMapping('/items') on class, @GetMapping('/welcome') on method. Python: @app.route('/items/welcome'). Go: http.HandleFunc('/items/welcome', handler). Rust: web::resource('/items').route(web::get().to(handler)). EK9: 'Items :/items' on service, 'method() as GET for :/welcome' on method.","keywords":["E07790","HTTP","REST","dict","endpoint","http","mapping","path","route","service","uri"],"primaryTopics":[],"typicalErrors":[{"error":"E50001","correct":"stdout.println(\"Service URI mapping:\")","incorrect":"stdout.println(getMapping())","explanation":"The function getMapping is not defined in this module. Use string literals or defined functions. See ek9 -h E50001 for details."}],"companions":[]}
{"id":658,"category":"Web Services","question":"How do I extract path parameters in EK9 services?","url":"https://ek9.io/qa/QA0658.html","alternatePhrasings":["How does PATH binding work in EK9 services?","How do I get URL path variables in service methods?","What is E07880 service parameter binding error?"],"answer":"EK9 services extract path parameters using :/{paramName} syntax. The path variable binds to a method parameter of the same name.\n\nPATH PARAMETER\nDeclare a path variable in the URI:\n  byId() as GET for :/{orderId}\n    -> orderId as String\nThe server extracts orderId from the URL path.\n\nCAPTURE PATTERN FOR DYNAMIC RESPONSE\nPath parameters are NOT in scope inside the dynamic HTTPResponse class body. To use a path parameter in response content, capture it in the dynamic class expression:\n  <- response as HTTPResponse: (capturedId: orderId) with trait HTTPResponse\n    override content()\n      <- rtn as String: `{\"id\": \"${capturedId}\"}`\nThe captured variable is accessible within the dynamic class methods.\n\nCONTENT BINDING\nUse :=: CONTENT for request body:\n  operator += :/\n    -> body as String :=: CONTENT\nThe request body is bound to the 'body' parameter.\n\nHTTPRESPONSE RETURN\nEvery service method must return HTTPResponse:\n  <- response as HTTPResponse: ...\n\nSee Q657 for URI mapping basics. See Q200 for CRUD operators. See Q201 for HTTP responses. See Q659 for HTTPResponse requirements. See Q660 for CRUD operators.","ek9Example":"defines module qa.webdeep.pathbinding\n\n  defines service\n\n    <?-\n      Service demonstrating path parameter extraction\n      and content body binding.\n    -?>\n    Orders :/orders open\n\n      // GET /orders/{orderId} — extract path param\n      byId() as GET for :/{orderId}\n        -> orderId as String\n        <- response as HTTPResponse: (capturedId: orderId) with trait HTTPResponse\n          override content()\n            <- rtn as String: `{\"orderId\": \"${capturedId}\"}`\n          override status() as pure\n            <- rtn as Integer: 200\n          override contentType() as pure\n            <- rtn as String: \"application/json\"\n          override cacheControl() as pure\n            <- rtn as String: \"no-cache\"\n          override contentLanguage() as pure\n            <- rtn as String: \"en\"\n          default operator ?\n\n      // POST /orders — content body binding\n      operator += :/\n        -> orderJson as String :=: CONTENT\n        <- response as HTTPResponse: () with trait HTTPResponse\n          override content()\n            <- rtn as String: `{\"received\": true}`\n          override status() as pure\n            <- rtn as Integer: 201\n          override contentType() as pure\n            <- rtn as String: \"application/json\"\n          override cacheControl() as pure\n            <- rtn as String: \"no-store\"\n          override contentLanguage() as pure\n            <- rtn as String: \"en\"\n          default operator ?\n\n  defines application\n\n    OrderApp\n      register Orders()\n\n  defines program\n\n    PathParameterDemo()\n      stdout <- Stdout()\n\n      stdout.println(\"Path parameter binding:\")\n      stdout.println(\"  GET /orders/{orderId} -> byId(orderId)\")\n      stdout.println(\"  POST /orders with CONTENT -> operator +=\")","migrationContext":"Java: @PathVariable('orderId') String orderId. Python: Flask route('<orderId>'). Go: mux.Vars(r)['orderId']. Rust: web::Path<String>. EK9: ':/{orderId}' in URI, '-> orderId as String' in parameter list.","keywords":["E07880","REST","URL","binding","extract","http","parameter","path","service","variable"],"primaryTopics":[],"typicalErrors":[{"error":"E07700","correct":"-> orderId as String","incorrect":"-> orderNumber as String","explanation":"Path parameters in the URI must have a matching method parameter with the same name. If the URI declares {orderId}, the method must have a parameter named orderId. See ek9 -h E07700 for details."}],"companions":[]}
{"id":659,"category":"Web Services","question":"Why must every service method return HTTPResponse?","url":"https://ek9.io/qa/QA0659.html","alternatePhrasings":["What is E07800 service missing return?","What return type do EK9 service methods require?","How do I construct an HTTPResponse in a service?"],"answer":"Every service method and operator must return HTTPResponse. The response encapsulates the HTTP status code, content body, content type, cache control, and language.\n\nHTTPRESPONSE TRAIT\nThe HTTPResponse trait defines the contract:\n  status() — HTTP status code (200, 201, 404, etc.)\n  content() — response body as String\n  contentType() — MIME type (application/json, text/html, etc.)\n  cacheControl() — caching directives\n  contentLanguage() — response language\n\nDYNAMIC CLASS PATTERN\nCreate responses with anonymous dynamic classes:\n  <- response as HTTPResponse: () with trait HTTPResponse\n    override content()\n      <- rtn as String: \"response body\"\n    override status() as pure\n      <- rtn as Integer: 200\n    ...\n\nWHY REQUIRED\nThe web server infrastructure needs standardised response data. Without HTTPResponse, the server cannot send status codes, headers, or content back to clients.\n\nSee Q201 for HTTP response details. See Q657 for URI mapping. See Q200 for CRUD operators.","ek9Example":"defines module qa.webdeep.returntype\n\n  defines service\n\n    <?-\n      Service with methods that correctly return HTTPResponse.\n      Every method must satisfy the HTTPResponse trait contract.\n    -?>\n    Health :/health open\n\n      // GET /health/status — returns 200 with JSON body\n      healthCheck() as GET for :/status\n        <- response as HTTPResponse: () with trait HTTPResponse\n          override content()\n            <- rtn as String: `{\"status\": \"healthy\"}`\n          override status() as pure\n            <- rtn as Integer: 200\n          override contentType() as pure\n            <- rtn as String: \"application/json\"\n          override cacheControl() as pure\n            <- rtn as String: \"no-cache\"\n          override contentLanguage() as pure\n            <- rtn as String: \"en\"\n          default operator ?\n\n      // GET /health/version — returns version info\n      version() as GET for :/version\n        <- response as HTTPResponse: () with trait HTTPResponse\n          override content()\n            <- rtn as String: `{\"version\": \"1.0.0\"}`\n          override status() as pure\n            <- rtn as Integer: 200\n          override contentType() as pure\n            <- rtn as String: \"application/json\"\n          override cacheControl() as pure\n            <- rtn as String: \"max-age=3600\"\n          override contentLanguage() as pure\n            <- rtn as String: \"en\"\n          default operator ?\n\n  defines application\n\n    HealthApp\n      register Health()\n\n  defines program\n\n    ServiceReturnTypeDemo()\n      stdout <- Stdout()\n\n      stdout.println(\"All service methods return HTTPResponse:\")\n      stdout.println(\"  status() -> HTTP status code\")\n      stdout.println(\"  content() -> response body\")\n      stdout.println(\"  contentType() -> MIME type\")\n      stdout.println(\"  cacheControl() -> caching rules\")","migrationContext":"Java: Spring ResponseEntity<T> or JAX-RS Response. Python: Flask return (body, status_code). Go: w.WriteHeader(status); w.Write(body). Rust: HttpResponse::Ok().json(). EK9: HTTPResponse trait with status(), content(), contentType(), cacheControl(), contentLanguage().","keywords":["E07800","HTTPResponse","content","function","http","required","return","service","status","trait"],"primaryTopics":[],"typicalErrors":[{"error":"E50001","correct":"stdout.println(\"All service methods return HTTPResponse:\")","incorrect":"stdout.println(getReturnInfo())","explanation":"The function getReturnInfo is not defined in this module. See ek9 -h E50001 for details."}],"companions":[]}
{"id":660,"category":"Web Services","question":"How do CRUD operators map to URIs with path parameters?","url":"https://ek9.io/qa/QA0660.html","alternatePhrasings":["How do I use += for POST and -= for DELETE in services?","What is the operator to HTTP verb mapping in EK9?","How do service operators handle path parameters?"],"answer":"EK9 maps service operators to HTTP verbs with semantic meaning. Each operator has a natural CRUD mapping.\n\nOPERATOR MAPPING\n  operator += :/           POST (create resource)\n  operator -= :/{id}       DELETE (remove resource)\n  operator :~: :/{id}      PATCH (partial update)\n  operator :^: :/{id}      PUT (full replacement)\n\nPATH PARAMETERS IN OPERATORS\nOperators can include path parameters:\n  operator -= :/{resourceId}\n    -> resourceId as String\n    <- response as HTTPResponse: ...\n\nCONTENT BINDING IN OPERATORS\nPOST and PUT operators accept request body:\n  operator += :/\n    -> body as String :=: CONTENT\n\nSEMANTIC ALIGNMENT\nThe operator meanings align with HTTP:\n  += adds to collection -> POST creates\n  -= removes from collection -> DELETE removes\n  :~: merges partially -> PATCH updates fields\n  :^: replaces entirely -> PUT replaces\n\nSee Q200 for CRUD overview. See Q657 for URI mapping. See Q658 for path parameters.","ek9Example":"defines module qa.webdeep.crudoperators\n\n  defines service\n\n    <?-\n      Full CRUD service using operator-to-verb mapping.\n      Each operator handles a different HTTP method.\n    -?>\n    Products :/products open\n\n      // GET /products — list\n      listProducts() :/\n        <- response as HTTPResponse: () with trait HTTPResponse\n          override content()\n            <- rtn as String: `[\"widget\", \"gadget\"]`\n          override status() as pure\n            <- rtn as Integer: 200\n          override contentType() as pure\n            <- rtn as String: \"application/json\"\n          override cacheControl() as pure\n            <- rtn as String: \"max-age=30\"\n          override contentLanguage() as pure\n            <- rtn as String: \"en\"\n          default operator ?\n\n      // POST /products — create\n      operator += :/\n        -> productJson as String :=: CONTENT\n        <- response as HTTPResponse: () with trait HTTPResponse\n          override content()\n            <- rtn as String: `{\"created\": true}`\n          override status() as pure\n            <- rtn as Integer: 201\n          override contentType() as pure\n            <- rtn as String: \"application/json\"\n          override cacheControl() as pure\n            <- rtn as String: \"no-store\"\n          override contentLanguage() as pure\n            <- rtn as String: \"en\"\n          default operator ?\n\n      // DELETE /products/{productId} — remove\n      operator -= :/{productId}\n        -> productId as String\n        <- response as HTTPResponse: () with trait HTTPResponse\n          override content()\n            <- rtn as String: \"\"\n          override status() as pure\n            <- rtn as Integer: 204\n          override contentType() as pure\n            <- rtn as String: \"text/plain\"\n          override cacheControl() as pure\n            <- rtn as String: \"no-store\"\n          override contentLanguage() as pure\n            <- rtn as String: \"en\"\n          default operator ?\n\n  defines application\n\n    ProductApp\n      register Products()\n\n  defines program\n\n    CrudOperatorDemo()\n      stdout <- Stdout()\n\n      stdout.println(\"CRUD operators in service:\")\n      stdout.println(\"  += maps to POST /products\")\n      stdout.println(\"  -= maps to DELETE /products/{id}\")","migrationContext":"Java: Spring @PostMapping, @DeleteMapping, @PatchMapping, @PutMapping. Python: Flask methods=['POST']. Go: manual method switch. Rust: actix .post()/.delete(). EK9: operators += (POST), -= (DELETE), :~: (PATCH), :^: (PUT) with URI paths.","keywords":["CRUD","DELETE","E07860","PATCH","POST","PUT","dict","http","mapping","operator","service"],"primaryTopics":[],"typicalErrors":[{"error":"E50001","correct":"stdout.println(\"CRUD operators in service:\")","incorrect":"stdout.println(getCrudInfo())","explanation":"The function getCrudInfo is not defined in this module. See ek9 -h E50001 for details."},{"error":"E50001","correct":"stdout.println(\"  += maps to POST /products\")","incorrect":"stdout.println(getPostInfo())","explanation":"The function getPostInfo is not defined in this module. See ek9 -h E50001 for details."}],"companions":[]}
{"id":661,"category":"Security and Sanitization","question":"Why can sanitized only be used on incoming String parameters?","url":"https://ek9.io/qa/QA0661.html","alternatePhrasings":["What is E07910 sanitized on wrong type?","What is E07920 sanitized in wrong location?","Can I mark fields or return values as sanitized?"],"answer":"The 'sanitized' modifier can ONLY be applied to INCOMING String parameters. It works by injecting call-site sanitization code that creates defensive copies.\n\nWHY ONLY STRING\nSanitization prevents injection attacks (SQL, XSS, command injection). These attacks exploit string interpolation into commands or markup. Non-string types like Integer or Boolean don't have this attack vector (E07910).\n\nWHY ONLY INCOMING PARAMETERS\nSanitized works by injecting code at the CALL SITE. Fields have no call site (they're initialized directly). Return parameters flow OUT, not IN. Local variables are internal with no external data entry point (E07920).\n\nCORRECT PATTERN\n  processInput()\n    -> userInput as sanitized String\nThe caller's string is copied and sanitized before the function body runs.\n\nCONSTRUCTOR PATTERN\nTo sanitize data stored in fields, sanitize the constructor parameter:\n  SafeRecord()\n    -> input as sanitized String\n    name :=? input\n\nSee Q215 for sanitized basics. See Q217 for sanitized with purity. See Q662 for safe copy patterns.","ek9Example":"defines module qa.sanitizeddeep.paramonly\n\n  defines function\n\n    <?-\n      Sanitized on incoming String parameter.\n      The compiler injects call-site sanitization code\n      that creates a defensive copy before the body runs.\n    -?>\n    processUserInput()\n      -> userInput as sanitized String\n      <- result as String: \"\"\n\n      safeCopy <- String(userInput)\n      result: safeCopy\n\n    <?-\n      Sanitized in constructor parameter.\n      Fields get sanitized data through constructor injection.\n    -?>\n    buildGreeting()\n      -> userName as sanitized String\n      <- greeting as String: \"\"\n\n      greeting: \"Hello, \" + userName\n\n  defines class\n\n    <?-\n      Class that accepts sanitized input through constructor.\n      The field stores already-sanitized data.\n    -?>\n    SafeRecord\n      storedName as String: String()\n\n      SafeRecord()\n        -> inputName as sanitized String\n        storedName :=? inputName\n\n      getName() as pure\n        <- rtn as String: storedName\n\n      default operator ?\n\n  defines program\n\n    SanitizedParameterDemo()\n      stdout <- Stdout()\n\n      cleaned <- processUserInput(\"safe text\")\n      stdout.println(`Processed: ${cleaned}`)\n\n      message <- buildGreeting(\"Alice\")\n      stdout.println(message)\n\n      record <- SafeRecord(\"Bob\")\n      stdout.println(`Stored: ${record.getName()}`)","migrationContext":"Java: manual InputValidation.sanitize(input). Python: bleach.clean(). Go: html.EscapeString(). Rust: ammonia::clean(). EK9: 'sanitized' modifier on String parameters with automatic call-site injection.","keywords":["E07910","E07920","String","call-site","injection","parameter","sanitize","sanitized","security","validate"],"primaryTopics":[],"typicalErrors":[{"error":"E07910","correct":"-> userInput as sanitized String","incorrect":"-> userInput as sanitized Integer","explanation":"The sanitized modifier can only be applied to String parameters. Non-string types like Integer have no injection attack vector. See ek9 -h E07910 for details."},{"error":"E50060","correct":"stdout.println(`Stored: ${record.getName()}`)","incorrect":"stdout.println(record.getName().toUpperCase())","explanation":"String has no toUpperCase() method in EK9. Use upperCase() instead. See ek9 -h E50060 for details."}],"companions":[]}
{"id":662,"category":"Security and Sanitization","question":"How do I safely work with sanitized parameters without aliasing?","url":"https://ek9.io/qa/QA0662.html","alternatePhrasings":["What is E07930 assignment from sanitized?","Why can't I assign a sanitized parameter to a local variable?","How do I copy a sanitized value in pure and non-pure contexts?"],"answer":"Direct assignment FROM a sanitized parameter creates hidden aliasing (E07930). Both variables point to the SAME sanitized copy, so mutations to one affect the other.\n\nHIDDEN ALIASING\n  someFunc(input as sanitized String)\n    alias <- input       // BOTH point to same memory!\nBoth 'alias' and 'input' share the same defensive copy.\n\nPURE CONTEXT: COPY CONSTRUCTOR ONLY\n  pureProcess() as pure\n    -> input as sanitized String\n    localCopy <- String(input)    // Creates NEW independent copy\n\nNON-PURE CONTEXT: MORE OPTIONS\n  nonPureProcess()\n    -> input as sanitized String\n    safeCopy <- String(input)     // Option 1: copy constructor\n    target: \"\"\n    target :=: safeCopy           // Option 2: copy operator on initialized var\n\nDIRECT EXPRESSION USE\n  directUse() as pure\n    -> input as sanitized String\n    result: \"Output: \" + input    // OK: concatenation creates new value\n\nThe copy constructor pattern works in ALL contexts and is the recommended approach.\n\nSee Q661 for sanitized parameter basics. See Q215 for sanitized overview. See Q663 for override matching.","ek9Example":"defines module qa.sanitizeddeep.safecopy\n\n  defines function\n\n    <?-\n      Pure context: copy constructor is the ONLY safe option.\n      Direct assignment would create hidden alias to sanitized copy.\n    -?>\n    pureExtract() as pure\n      -> rawInput as sanitized String\n      <- cleaned as String: \"\"\n\n      safeCopy <- String(rawInput)\n      cleaned: safeCopy\n\n    <?-\n      Direct expression use works without assignment.\n      String concatenation creates a new value, not an alias.\n    -?>\n    formatMessage() as pure\n      -> userMessage as sanitized String\n      <- formatted as String: \"\"\n\n      formatted: \"Message: \" + userMessage\n\n    <?-\n      Non-pure context: copy constructor works here too.\n      This is the universal safe pattern.\n    -?>\n    logAndProcess()\n      -> inputData as sanitized String\n      <- processed as String: \"\"\n\n      localCopy <- String(inputData)\n      processed: localCopy\n\n    <?-\n      Passing sanitized parameter to another function is always safe.\n      The receiving function gets the sanitized copy, no alias created.\n    -?>\n    delegateWork()\n      -> externalInput as sanitized String\n      <- result as String: \"\"\n\n      result: formatMessage(externalInput)\n\n  defines program\n\n    SafeCopyDemo()\n      stdout <- Stdout()\n\n      cleanResult <- pureExtract(\"user data\")\n      stdout.println(`Pure extract: ${cleanResult}`)\n\n      formatted <- formatMessage(\"hello world\")\n      stdout.println(formatted)\n\n      logged <- logAndProcess(\"log entry\")\n      stdout.println(`Logged: ${logged}`)\n\n      delegated <- delegateWork(\"delegated input\")\n      stdout.println(delegated)","migrationContext":"Java: manual String copying with new String(). Python: copy.copy() for defensive copies. Go: strings are immutable (value semantics). Rust: clone(). EK9: String(input) copy constructor prevents aliasing from sanitized parameters.","keywords":["E07930","String","aliasing","assignment","copy","defensive","immutable","pure","sanitize","sanitized","security","side-effect","validate"],"primaryTopics":[],"typicalErrors":[{"error":"E07930","correct":"safeCopy <- String(rawInput)","incorrect":"safeCopy <- rawInput","explanation":"Direct assignment from a sanitized parameter creates hidden aliasing where both variables point to the same sanitized copy. Use the copy constructor String(param) to create an independent copy. See ek9 -h E07930 for details."}],"companions":[]}
{"id":663,"category":"Security and Sanitization","question":"Why must sanitized match exactly in overridden methods?","url":"https://ek9.io/qa/QA0663.html","alternatePhrasings":["What is E07940 sanitized mismatch in override?","Can I add sanitized to an override method?","What happens if I remove sanitized from an overriding method?"],"answer":"The 'sanitized' modifier must MATCH between a method and the method it overrides (E07940). The Liskov Substitution Principle requires consistent security behavior through polymorphic dispatch.\n\nWHY MATCH IS REQUIRED\nConsider polymorphic dispatch:\n  handler <- getHandler()      // Could be Base or Derived\n  handler.process(userInput)   // Is it sanitized or not?\nIf sanitized doesn't match, security depends on runtime type.\n\nTWO FAILURE CASES\nCase 1: Super has sanitized, override removes it\n  -> Caller expects sanitization but override skips it = vulnerability\nCase 2: Super has no sanitized, override adds it\n  -> Caller doesn't expect data modification = LSP violation\n\nCORRECT PATTERN\nBoth must agree:\n  defines class Base as open\n    process()\n      -> input as sanitized String\n  defines class Derived extends Base\n    override process()\n      -> input as sanitized String    // Must match!\n\nTRAIT NOTE\nTraits cannot have 'default operator ?' (E07030). When defining a trait with sanitized methods, only classes implementing the trait can have 'default operator ?'.\n\nSee Q661 for sanitized basics. See Q662 for safe copy patterns. See Q664 for increment expression restriction. See Q215 for sanitized overview.","ek9Example":"defines module qa.sanitizeddeep.overridematch\n\n  defines trait\n\n    <?-\n      Trait defining sanitized contract.\n      All implementations must preserve sanitized on the parameter.\n    -?>\n    InputProcessor\n      process() as abstract\n        -> inputText as sanitized String\n        <- result as String?\n\n  defines class\n\n    <?-\n      Base class with sanitized parameter.\n      Subclasses MUST keep sanitized to maintain LSP.\n    -?>\n    BaseHandler as open\n      handleRequest()\n        -> requestBody as sanitized String\n        <- response as String: \"\"\n\n        safeCopy <- String(requestBody)\n        response: safeCopy\n\n      default operator ?\n\n    <?-\n      Correct override: sanitized matches the parent.\n      Security behavior is consistent through polymorphism.\n    -?>\n    DerivedHandler extends BaseHandler\n      override handleRequest()\n        -> requestBody as sanitized String\n        <- response as String: \"\"\n\n        safeCopy <- String(requestBody)\n        response: \"Derived: \" + safeCopy\n\n      default operator ?\n\n    <?-\n      Trait implementation with matching sanitized.\n    -?>\n    SafeProcessor with trait of InputProcessor\n      override process()\n        -> inputText as sanitized String\n        <- result as String: \"\"\n\n        localCopy <- String(inputText)\n        result: \"Processed: \" + localCopy\n\n      default operator ?\n\n  defines program\n\n    OverrideMatchDemo()\n      stdout <- Stdout()\n\n      baseHandler <- BaseHandler()\n      baseResponse <- baseHandler.handleRequest(\"base input\")\n      stdout.println(baseResponse)\n\n      derivedHandler <- DerivedHandler()\n      derivedResponse <- derivedHandler.handleRequest(\"derived input\")\n      stdout.println(derivedResponse)\n\n      processor <- SafeProcessor()\n      processResult <- processor.process(\"trait input\")\n      stdout.println(processResult)","migrationContext":"Java: no enforcement, override can change validation. Python: no enforcement. Go: no inheritance. Rust: trait implementations must match exactly. EK9: compiler enforces sanitized matching in overrides for LSP compliance.","keywords":["E07940","LSP","Liskov","abstract","function","handler","match","open","override","polymorphic","sanitize","sanitized","sealed","security","validate","virtual","visitor"],"primaryTopics":[],"typicalErrors":[{"error":"E50060","correct":"baseResponse <- baseHandler.handleRequest(\"base input\")","incorrect":"baseResponse <- baseHandler.handleRequest(\"base input\").toUpperCase()","explanation":"String has no toUpperCase() method in EK9. Use upperCase() instead. See ek9 -h E50060 for details."}],"companions":[]}
{"id":664,"category":"Security and Sanitization","question":"Why are ++ and -- statement-only operators in EK9?","url":"https://ek9.io/qa/QA0664.html","alternatePhrasings":["What is E07950 increment in expression context?","Why can't I use x++ in an assignment?","How do I increment and assign in EK9?"],"answer":"In EK9, '++' and '--' are STATEMENT-ONLY operators (E07950). They cannot be used in expressions where a value is expected.\n\nWHY REMOVED FROM EXPRESSIONS\nIn C/Java, 'y <- x++' has confusing semantics:\n  C: y gets the OLD value before increment (surprising!)\n  Java: y becomes an ALIAS to x (both reference same object!)\nBoth are sources of subtle bugs.\n\nCORRECT PATTERN\n  x++                    // Statement only: increments x\n  y <- x                 // Assign separately if needed\n  // Or use explicit arithmetic:\n  y <- x + 1             // Clear intent, predictable\n\nSTATEMENT VS EXPRESSION\nStatement context (allowed):\n  counter++              // Standalone increment\n  counter--              // Standalone decrement\nExpression context (not allowed):\n  result <- counter++    // ERROR: no value returned\n  list += counter--      // ERROR: no value returned\n\nSee Q50 for loop alternatives. See Q283 for loop patterns without break.","ek9Example":"defines module qa.sanitizeddeep.incrementstatement\n\n  defines program\n\n    IncrementStatementDemo()\n      stdout <- Stdout()\n\n      // === CORRECT: Statement-only increment ===\n\n      counter <- 0\n\n      counter++\n      stdout.println(`After increment: ${counter}`)\n\n      counter++\n      stdout.println(`After second increment: ${counter}`)\n\n      counter--\n      stdout.println(`After decrement: ${counter}`)\n\n      // === CORRECT: Explicit arithmetic for expressions ===\n\n      baseValue <- 10\n      nextValue <- baseValue + 1\n      stdout.println(`Next value: ${nextValue}`)\n\n      previousValue <- baseValue - 1\n      stdout.println(`Previous value: ${previousValue}`)\n\n      // === CORRECT: Loop with separate increment ===\n\n      loopCount <- 0\n      iterations <- 5\n      while loopCount < iterations\n        stdout.println(`Iteration ${loopCount}`)\n        loopCount++","migrationContext":"Java: x++ returns old value, ++x returns new value (confusing). C++: same semantics, major bug source. Python: no ++ operator. Go: ++ is statement-only (same as EK9). Rust: no ++ operator. EK9: ++ and -- are statement-only, use explicit arithmetic for expressions.","keywords":["E07950","decrement","expression","increment","migrate","mutation","operator","sanitize","security","statement","validate"],"primaryTopics":[],"typicalErrors":[{"error":"E50050","correct":"counter++","incorrect":"nextValue <- counter++","explanation":"The ++ and -- operators are statement-only in EK9. They cannot be used in expression context where a value is expected. Use explicit arithmetic like 'nextValue <- counter + 1' instead. See ek9 -h E50050 for details."}],"companions":[]}
{"id":665,"category":"Security and Sanitization","question":"How do sanitized parameters work with web service content binding?","url":"https://ek9.io/qa/QA0665.html","alternatePhrasings":["Can I use sanitized with CONTENT binding in services?","How do I sanitize request bodies in EK9 services?","How does sanitized interact with service operators?"],"answer":"Sanitized parameters combine naturally with service content binding. When a service operator receives request body content via :=: CONTENT, the parameter can be marked sanitized for automatic input cleaning.\n\nSANITIZED CONTENT BINDING\n  operator += :/\n    -> body as sanitized String :=: CONTENT\nThe request body is both bound from HTTP content AND sanitized before the operator body runs.\n\nSANITIZED PATH PARAMETERS\n  byId() as GET for :/{resourceId}\n    -> resourceId as sanitized String\nPath parameters from URLs can also be sanitized.\n\nPATTERN: SAFE API\nCombine sanitized with copy constructor in service methods:\n  operator += :/\n    -> payload as sanitized String :=: CONTENT\n    safeCopy <- String(payload)\n    // Process safeCopy safely\n\nSee Q661 for sanitized basics. See Q662 for copy patterns. See Q657 for URI mapping. See Q658 for path parameters.","ek9Example":"defines module qa.sanitizeddeep.serviceintegration\n\n  defines function\n\n    <?-\n      Standalone function demonstrating sanitized parameter\n      in a function context, similar to how service methods work.\n    -?>\n    processPayload()\n      -> payload as sanitized String\n      <- result as String: \"\"\n\n      safeCopy <- String(payload)\n      result: \"Received: \" + safeCopy\n\n    <?-\n      Function that accepts sanitized input and delegates.\n    -?>\n    handleRequest()\n      -> requestBody as sanitized String\n      <- response as String: \"\"\n\n      response: processPayload(requestBody)\n\n  defines service\n\n    <?-\n      Service with sanitized content binding on POST.\n      The request body is sanitized before the operator body runs.\n    -?>\n    SecureApi :/api open\n\n      // GET /api/status — no sanitization needed for read\n      status() :/status\n        <- response as HTTPResponse: () with trait HTTPResponse\n          override content()\n            <- rtn as String: `{\"status\": \"ok\"}`\n          override status() as pure\n            <- rtn as Integer: 200\n          override contentType() as pure\n            <- rtn as String: \"application/json\"\n          override cacheControl() as pure\n            <- rtn as String: \"no-cache\"\n          override contentLanguage() as pure\n            <- rtn as String: \"en\"\n          default operator ?\n\n      // POST /api — sanitized content binding\n      operator += :/\n        -> payload as sanitized String :=: CONTENT\n        <- response as HTTPResponse: () with trait HTTPResponse\n          override content()\n            <- rtn as String: `{\"accepted\": true}`\n          override status() as pure\n            <- rtn as Integer: 201\n          override contentType() as pure\n            <- rtn as String: \"application/json\"\n          override cacheControl() as pure\n            <- rtn as String: \"no-store\"\n          override contentLanguage() as pure\n            <- rtn as String: \"en\"\n          default operator ?\n\n  defines application\n\n    SecureApiApp\n      register SecureApi()\n\n  defines program\n\n    SanitizedServiceDemo()\n      stdout <- Stdout()\n\n      stdout.println(\"Sanitized service integration:\")\n      stdout.println(\"  POST /api with sanitized CONTENT binding\")\n      stdout.println(\"  Automatic injection prevention on request body\")\n\n      processed <- processPayload(\"safe user data\")\n      stdout.println(processed)","migrationContext":"Java: Spring @RequestBody with manual validation. Python: Flask request.get_json() with schema validation. Go: manual sanitization after body read. Rust: actix extractors with validation. EK9: sanitized modifier on CONTENT-bound parameter provides automatic call-site sanitization.","keywords":["CONTENT","E07910","E07930","binding","sanitize","sanitized","security","service","validate","web"],"primaryTopics":[],"typicalErrors":[{"error":"E07910","correct":"-> payload as sanitized String :=: CONTENT","incorrect":"-> payload as sanitized Integer :=: CONTENT","explanation":"The sanitized modifier can only be applied to String parameters. Injection attacks exploit string interpolation, so non-string types have no attack vector. See ek9 -h E07910 for details."},{"error":"E07930","correct":"safeCopy <- String(payload)","incorrect":"safeCopy <- payload","explanation":"Direct assignment from a sanitized parameter creates hidden aliasing. Use the copy constructor String(param) to create an independent copy that avoids shared mutable state. See ek9 -h E07930 for details."}],"companions":[]}
{"id":666,"category":"DI Validation","question":"Why can't I inject components in a pure context?","url":"https://ek9.io/qa/QA0666.html","alternatePhrasings":["What is E08140 injection in pure context?","Why does injection conflict with purity?","How do I use components in pure methods?"],"answer":"Component injection is an impure operation because it involves runtime state management (E08140). The DI container resolves and provides instances at runtime, which conflicts with purity guarantees.\n\nWHY INJECTION IS IMPURE\nPure functions guarantee no side effects and deterministic results. Injection accesses mutable singleton state managed by the DI container, making the result non-deterministic.\n\nCORRECT PATTERN\nInject at the field level, then pass data to pure methods:\n  defines component\n    MyComponent\n      repository as Repository!\n\n      processData() as pure\n        -> items as List of String\n        <- count as Integer: length items\n\nThe impure injection happens at field level. Pure methods receive data through parameters.\n\nSee Q111 for component basics. See Q560 for purity contracts. See Q667 for abstract injection.","ek9Example":"defines module qa.divalidation.purecontext\n\n  defines component\n\n    <?-\n      Abstract component defining repository contract.\n    -?>\n    DataStore as abstract\n\n      fetchAll() as abstract\n        <- rtn as List of String?\n\n      default operator ?\n\n    <?-\n      Concrete implementation of the data store.\n    -?>\n    InMemoryStore extends DataStore\n\n      override fetchAll()\n        <- rtn <- List() of String\n        rtn += \"record-alpha\"\n        rtn += \"record-beta\"\n\n      default operator ?\n\n  defines function\n\n    <?-\n      Pure function receives data as parameter,\n      not through injection. This is the correct pattern\n      for processing data without DI coupling.\n    -?>\n    countRecords() as pure\n      -> records as List of String\n      <- total as Integer: length records\n\n  defines application\n\n    DataApp\n      register InMemoryStore() as DataStore\n\n  defines program\n\n    InjectionPureContextDemo() with application of DataApp\n      stdout <- Stdout()\n\n      store as DataStore!\n\n      allRecords <- store.fetchAll()\n      stdout.println(`Records fetched: ${length allRecords}`)\n\n      recordCount <- countRecords(allRecords)\n      stdout.println(`Count via pure function: ${recordCount}`)","migrationContext":"Java: Spring allows injection everywhere (no purity concept). Python: no DI purity enforcement. Go: no DI framework. Rust: no runtime DI. EK9: injection is impure by design, use parameter passing for pure methods.","keywords":["DI","E08140","component","context","immutable","impure","inject","injection","pure","registration","side-effect","text","validate"],"primaryTopics":[],"typicalErrors":[{"error":"E08140","correct":"      override fetchAll()\n        <- rtn <- List() of String\n        rtn += \"record-alpha\"\n        rtn += \"record-beta\"","incorrect":"      override fetchAll()\n        <- rtn <- List() of String\n        rtn += \"record-alpha\"\n        rtn += \"record-beta\"\n\n      pureButBroken() as pure\n        injected as DataStore!\n        require injected?","explanation":"Component injection with '!' is an impure operation (accesses DI container state). A pure method cannot contain injection points. Pass the component as a parameter instead. See ek9 -h E08140 for details."}],"companions":[]}
{"id":667,"category":"DI Validation","question":"Why must injected components be abstract types?","url":"https://ek9.io/qa/QA0667.html","alternatePhrasings":["What is E08150 injecting concrete component?","Why can't I inject a concrete component class?","How does abstract injection enable testing?"],"answer":"EK9 requires injected component types to be abstract (E08150). This enforces the Dependency Inversion Principle: depend on abstractions, not implementations.\n\nWHY ABSTRACT ONLY\nConcrete injection creates tight coupling. Abstract injection allows:\n  1. Different applications wire different implementations\n  2. Test applications inject mocks\n  3. Production applications inject real services\n\nREGISTRATION MUST USE 'AS'\nConcrete components must be registered with 'as AbstractType':\n  register ConsoleLogger() as Logger    // Correct\n  register ConsoleLogger()              // ERROR: E50020\nWithout 'as', the registration genus is incompatible.\n\nINJECTION MUST USE ABSTRACT TYPE\n  logger as Logger!                     // Correct: abstract\n  logger as ConsoleLogger!              // ERROR: E08150\n\nPATTERN\n  defines component\n    Logger as abstract\n      log() as abstract\n        -> message as String\n    ConsoleLogger extends Logger\n      override log()\n        -> message as String\n        stdout <- Stdout()\n        stdout.println(message)\n\n  defines application\n    ProdApp\n      register ConsoleLogger() as Logger\n\n  defines program\n    MyProgram with application of ProdApp\n      logger as Logger!       // Inject abstract\n\nSee Q111 for component basics. See Q666 for pure context. See Q668 for injectable contexts. See Q324 for Autowired equivalent. See Q325 for Component equivalent.","ek9Example":"defines module qa.divalidation.abstractonly\n\n  defines component\n\n    <?-\n      Abstract logger contract.\n      Concrete implementations are registered in applications.\n    -?>\n    Logger as abstract\n\n      logMessage() as abstract\n        -> logEntry as String\n\n      default operator ?\n\n    <?-\n      Console logger implementation.\n    -?>\n    ConsoleLogger extends Logger\n\n      override logMessage()\n        -> logEntry as String\n        stdout <- Stdout()\n        stdout.println(logEntry)\n\n      default operator ?\n\n    <?-\n      Abstract repository contract.\n    -?>\n    UserRepository as abstract\n\n      findByName() as abstract\n        -> userName as String\n        <- result as String?\n\n      default operator ?\n\n    <?-\n      In-memory repository implementation.\n    -?>\n    InMemoryUserRepo extends UserRepository\n\n      override findByName()\n        -> userName as String\n        <- result as String: \"\"\n\n        result: \"Found: \" + userName\n\n      default operator ?\n\n  defines application\n\n    ProductionApp\n      register ConsoleLogger() as Logger\n      register InMemoryUserRepo() as UserRepository\n\n  defines program\n\n    AbstractInjectionDemo() with application of ProductionApp\n      stdout <- Stdout()\n\n      logger as Logger!\n      userRepo as UserRepository!\n\n      logger.logMessage(\"Starting application\")\n\n      foundUser <- userRepo.findByName(\"Alice\")\n      stdout.println(foundUser)\n\n      logger.logMessage(\"Application complete\")","migrationContext":"Java: Spring injects concrete types (bad practice). Python: duck typing, no enforcement. Go: interfaces encouraged but not required. Rust: trait objects for abstraction. EK9: compiler enforces abstract injection, concrete types cannot be injected.","keywords":["DI","E08150","abstract","component","concrete","inject","injection","interface","registration","validate"],"primaryTopics":[],"typicalErrors":[{"error":"E08150","correct":"logger as Logger!","incorrect":"logger as ConsoleLogger!","explanation":"Only abstract component types can be injected. Injecting a concrete type like ConsoleLogger creates tight coupling and defeats the purpose of dependency injection. Depend on the abstract type Logger and let the application wiring choose the implementation. See ek9 -h E08150 for details."}],"companions":[]}
{"id":668,"category":"DI Validation","question":"Which constructs support component injection?","url":"https://ek9.io/qa/QA0668.html","alternatePhrasings":["What is E08160 injection not possible in context?","Can I inject in functions or records?","Where can I use the ! injection suffix?"],"answer":"Only specific EK9 constructs support component injection with the '!' suffix (E08160). Injection is available in components, services, and programs linked to applications.\n\nINJECTABLE CONTEXTS\n  defines component — field injection\n  defines service — field injection\n  defines program — with application of AppName\n\nNON-INJECTABLE CONTEXTS\n  defines function — no DI container access\n  defines record — no DI container access\n  defines class — no DI container access (use component instead)\n\nPROGRAM REQUIREMENT\nPrograms must declare 'with application of AppName' to enable injection. Without this linkage, the compiler cannot resolve which implementations to inject (E08220).\n\nSee Q111 for component basics. See Q666 for pure context. See Q667 for abstract injection. See Q672 for program-application linking.","ek9Example":"defines module qa.divalidation.injectablecontexts\n\n  defines component\n\n    <?-\n      Abstract notification service contract.\n    -?>\n    NotificationService as abstract\n\n      sendNotification() as abstract\n        -> recipient as String\n\n      default operator ?\n\n    <?-\n      Email-based notification implementation.\n    -?>\n    EmailNotifier extends NotificationService\n\n      override sendNotification()\n        -> recipient as String\n        stdout <- Stdout()\n        stdout.println(`Email sent to ${recipient}`)\n\n      default operator ?\n\n  defines application\n\n    OrderApp\n      register EmailNotifier() as NotificationService\n\n  defines program\n\n    <?-\n      Program with injection: 'with application of' enables DI.\n      This is the correct injectable context for programs.\n    -?>\n    InjectableContextDemo() with application of OrderApp\n      stdout <- Stdout()\n\n      notifier as NotificationService!\n      notifier.sendNotification(\"demo-recipient\")\n\n      stdout.println(\"Injection works in programs linked to applications\")\n      stdout.println(\"Components and services also support injection\")","migrationContext":"Java: Spring @Autowired works in any managed bean. Python: inject anywhere with frameworks. Go: manual wiring. Rust: no DI framework. EK9: injection limited to components, services, and programs for architectural clarity.","keywords":["DI","E08160","component","constant","context","inject","injection","program","registration","service","validate"],"primaryTopics":[],"typicalErrors":[{"error":"E50001","correct":"InjectableContextDemo() with application of OrderApp\n      stdout <- Stdout()\n\n      notifier as NotificationService!","incorrect":"NonInjectableDemo()\n      notifier as NotificationService!","explanation":"Component injection with '!' is only valid in components, services, and programs linked to an application. Functions, records, and classes do not have access to the DI container. Programs must use 'with application of' to enable injection. See ek9 -h E50001 for details."}],"companions":[]}
{"id":669,"category":"DI Validation","question":"Why can't I reassign an injected component variable?","url":"https://ek9.io/qa/QA0669.html","alternatePhrasings":["What is E08170 reassignment of injected variable?","Can I change an injected dependency at runtime?","How do I handle optional injection?"],"answer":"Injected dependencies are set by the DI container and cannot be directly reassigned (E08170). The container manages the lifecycle, and manual reassignment would break the contract between application wiring and injection sites.\n\nWHY NO REASSIGNMENT\nThe application definition declares which concrete component satisfies each abstract injection point. If code reassigns the variable, the wiring becomes unpredictable.\n\nGUARDED ASSIGNMENT ALLOWED\nUse ':=?' for conditional fallback:\n  service as MyService!\n  service :=? fallbackService    // Only assign if not injected\n\nThis respects the container's decision: if injection succeeded, the guard does nothing.\n\nSee Q667 for abstract injection. See Q668 for injectable contexts. See Q670 for field initialization.","ek9Example":"defines module qa.divalidation.reassignment\n\n  defines component\n\n    <?-\n      Abstract cache contract.\n    -?>\n    CacheService as abstract\n\n      lookup() as abstract\n        -> cacheKey as String\n        <- cachedValue as String?\n\n      default operator ?\n\n    <?-\n      Simple in-memory cache implementation.\n    -?>\n    InMemoryCache extends CacheService\n\n      override lookup()\n        -> cacheKey as String\n        <- cachedValue as String: \"\"\n\n        cachedValue: \"cached:\" + cacheKey\n\n      default operator ?\n\n  defines application\n\n    CacheApp\n      register InMemoryCache() as CacheService\n\n  defines program\n\n    InjectionReassignmentDemo() with application of CacheApp\n      stdout <- Stdout()\n\n      cache as CacheService!\n      directLookup <- cache.lookup(\"settings\")\n      stdout.println(`Lookup: ${directLookup}`)\n\n      anotherLookup <- cache.lookup(\"user-profile\")\n      stdout.println(`Another: ${anotherLookup}`)","migrationContext":"Java: Spring injection is final by convention (not enforced). Python: no enforcement. Go: manual wiring can be changed. Rust: ownership prevents aliasing. EK9: compiler prevents reassignment of injected variables, guarded assignment allowed.","keywords":["DI","E08170","component","guarded","immutable","inject","injection","isset","null-safe","reassignment","registration","safe","validate"],"primaryTopics":[],"typicalErrors":[{"error":"E08170","correct":"      cache as CacheService!\n      directLookup <- cache.lookup(\"settings\")","incorrect":"      cache as CacheService!\n      cache := InMemoryCache()\n      directLookup <- cache.lookup(\"settings\")","explanation":"Injected component variables are set by the DI container and cannot be reassigned with ':='. The application wiring defines which implementation satisfies each injection point, and manual reassignment would break that contract. See ek9 -h E08170 for details."}],"companions":[]}
{"id":670,"category":"DI Validation","question":"Why must component fields be initialized or injected?","url":"https://ek9.io/qa/QA0670.html","alternatePhrasings":["What is E08180 property not initialized?","How do I initialize fields in components?","What is the difference between field initialization and injection?"],"answer":"Every component field must either have an initial value or be marked for injection with '!' (E08180). Uninitialized, non-injected fields leave the object in an undefined state.\n\nFIELD INITIALIZATION\n  defines component\n    MyComponent\n      counter as Integer: 0             // Initialized\n      items as List of String: List() of String  // Initialized\n\nFIELD INJECTION\n  defines component\n    MyComponent\n      repository as Repository!          // Injected by container\n\nBOTH IN SAME COMPONENT\n  defines component\n    OrderService\n      repository as Repository!          // Injected\n      retryLimit as Integer: 3           // Initialized\n\nSee Q667 for abstract injection. See Q669 for reassignment. See Q671 for circular dependencies.","ek9Example":"defines module qa.divalidation.fieldinit\n\n  defines component\n\n    <?-\n      Abstract message formatter contract.\n    -?>\n    MessageFormatter as abstract\n\n      formatText() as abstract\n        -> rawText as String\n        <- formatted as String?\n\n      default operator ?\n\n    <?-\n      Uppercase formatter implementation.\n    -?>\n    UpperCaseFormatter extends MessageFormatter\n\n      override formatText()\n        -> rawText as String\n        <- formatted as String: \"\"\n\n        formatted: rawText.upperCase()\n\n      default operator ?\n\n  defines application\n\n    NotificationApp\n      register UpperCaseFormatter() as MessageFormatter\n\n  defines program\n\n    FieldInitDemo() with application of NotificationApp\n      stdout <- Stdout()\n\n      formatter as MessageFormatter!\n      result <- formatter.formatText(\"hello world\")\n      stdout.println(`Formatted: ${result}`)","migrationContext":"Java: fields can be null (NPE risk). Python: fields set in __init__. Go: zero values for all fields. Rust: all fields must be initialized. EK9: fields must be initialized or injected, no uninitialized state allowed.","keywords":["DI","E08180","component","field","initialization","inject","injection","property","registration","validate"],"primaryTopics":[],"typicalErrors":[{"error":"E08180","correct":"formatter as MessageFormatter!","incorrect":"formatter as MessageFormatter","explanation":"Every component field must either be initialized with a value or marked for injection with '!'. A field declared without initialization and without the injection suffix leaves the object in an undefined state. Use '!' for DI-managed fields or provide an initial value. See ek9 -h E08180 for details."}],"companions":[]}
{"id":671,"category":"DI Validation","question":"How does EK9 prevent circular dependencies in DI?","url":"https://ek9.io/qa/QA0671.html","alternatePhrasings":["What is E08190 circular dependency detected?","How do I break a dependency cycle between components?","What happens when two components inject each other?"],"answer":"EK9 detects circular dependencies at compile time (E08190). When Component A injects Component B, and Component B injects Component A (directly or transitively), the compiler rejects the code.\n\nWHY CIRCULAR IS FORBIDDEN\nCircular dependencies make object creation impossible. To create A, you need B. To create B, you need A. This deadlock is detected before runtime.\n\nBREAKING CYCLES\n1. Introduce an intermediary:\n   A -> C <- B (C mediates between A and B)\n2. Use event-based communication\n3. Restructure to remove the cycle\n\nCORRECT HIERARCHICAL PATTERN\n  Repository -> (no dependencies)\n  Service -> Repository\n  Controller -> Service\nDependencies flow in ONE direction.\n\nSee Q667 for abstract injection. See Q670 for field initialization. See Q673 for missing registration.","ek9Example":"defines module qa.divalidation.circulardep\n\n  defines component\n\n    <?-\n      Abstract storage contract.\n      Bottom of the dependency hierarchy (no injections).\n    -?>\n    Storage as abstract\n\n      store() as abstract\n        -> payload as String\n        <- stored as Boolean?\n\n      default operator ?\n\n    <?-\n      Concrete storage implementation.\n    -?>\n    FileStorage extends Storage\n\n      override store()\n        -> payload as String\n        <- stored as Boolean: true\n\n      default operator ?\n\n    <?-\n      Abstract processing contract.\n      Depends on Storage (one direction).\n    -?>\n    Processor as abstract\n\n      processItem() as abstract\n        -> itemData as String\n        <- result as String?\n\n      default operator ?\n\n    <?-\n      Concrete processor that depends on storage.\n      Dependencies flow: Processor -> Storage (no cycle).\n    -?>\n    ItemProcessor extends Processor\n\n      storage as Storage!\n\n      override processItem()\n        -> itemData as String\n        <- result as String: \"\"\n\n        storedOk <- storage.store(itemData)\n        if storedOk?\n          result: \"Processed: \" + itemData\n\n      default operator ?\n\n  defines application\n\n    HierarchicalApp\n      register FileStorage() as Storage\n      register ItemProcessor() as Processor\n\n  defines program\n\n    CircularDependencyDemo() with application of HierarchicalApp\n      stdout <- Stdout()\n\n      processor as Processor!\n      result <- processor.processItem(\"test-data\")\n      stdout.println(result)\n\n      stdout.println(\"Dependencies flow one direction: Processor -> Storage\")\n      stdout.println(\"No circular dependency possible\")","migrationContext":"Java: Spring detects circular dependencies at runtime (throws exception). Python: no detection. Go: manual wiring avoids cycles. Rust: ownership prevents cycles. EK9: compiler detects cycles at compile time, before any code runs.","keywords":["DI","E08190","circular","component","cycle","dependency","inject","registration","validate","validation"],"primaryTopics":[],"typicalErrors":[{"error":"E08190","correct":"    FileStorage extends Storage\n\n      override store()\n        -> payload as String\n        <- stored as Boolean: true\n\n      default operator ?","incorrect":"    FileStorage extends Storage\n\n      processor as Processor!\n\n      override store()\n        -> payload as String\n        <- stored as Boolean: true\n\n      default operator ?","explanation":"Adding an injection of Processor into FileStorage creates a circular dependency: ItemProcessor depends on Storage (via FileStorage), and FileStorage now depends on Processor. Neither can be constructed first. See ek9 -h E08190 for details."}],"companions":[]}
{"id":672,"category":"DI Validation","question":"Why must programs declare 'with application of' for injection?","url":"https://ek9.io/qa/QA0672.html","alternatePhrasings":["What is E08220 program without application?","How do I link a program to an application?","What happens if I inject without an application?"],"answer":"Programs that use component injection must declare 'with application of AppName' (E08220). Without this linkage, the compiler cannot resolve which concrete implementations to inject.\n\nLINKAGE SYNTAX\n  defines program\n    MyProgram() with application of MyApp\n      service as AbstractService!    // Resolved from MyApp\n\nWHY REQUIRED\nThe application definition is the wiring blueprint. It maps abstract types to concrete implementations. Without it, the compiler sees an injection point but has no way to resolve it.\n\nMULTIPLE APPLICATIONS\nDifferent programs can link to different applications:\n  ProdProgram() with application of ProductionApp\n  TestProgram() with application of TestApp\nSame abstract types, different implementations.\n\nSee Q668 for injectable contexts. See Q673 for missing registration. See Q674 for duplicate registration.","ek9Example":"defines module qa.divalidation.programlink\n\n  defines component\n\n    <?-\n      Abstract audit service contract.\n    -?>\n    AuditService as abstract\n\n      recordEvent() as abstract\n        -> eventName as String\n\n      default operator ?\n\n    <?-\n      Console-based audit implementation.\n    -?>\n    ConsoleAudit extends AuditService\n\n      override recordEvent()\n        -> eventName as String\n        stdout <- Stdout()\n        stdout.println(`Audit: ${eventName}`)\n\n      default operator ?\n\n  defines application\n\n    <?-\n      Application wiring for production.\n      Maps abstract types to concrete implementations.\n    -?>\n    ProductionSetup\n      register ConsoleAudit() as AuditService\n\n  defines program\n\n    <?-\n      Program linked to application for injection.\n      The 'with application of' clause enables injection resolution.\n    -?>\n    ProgramApplicationDemo() with application of ProductionSetup\n      stdout <- Stdout()\n\n      auditor as AuditService!\n      auditor.recordEvent(\"application-started\")\n\n      stdout.println(\"Program linked to ProductionSetup application\")\n      stdout.println(\"Injection resolved through application wiring\")\n\n      auditor.recordEvent(\"demo-complete\")","migrationContext":"Java: Spring ApplicationContext is implicit (classpath scanning). Python: Flask/Django auto-discover. Go: manual wiring in main(). Rust: manual wiring. EK9: explicit 'with application of' declaration links program to its DI configuration.","keywords":["DI","E08220","application","define","inject","injection","linkage","program","registration","validate","wiring"],"primaryTopics":[],"typicalErrors":[{"error":"E08220","correct":"    ProgramApplicationDemo() with application of ProductionSetup","incorrect":"    ProgramApplicationDemo()","explanation":"Programs that use component injection must declare 'with application of AppName' to link to a DI configuration. Without this linkage, the compiler has no application wiring blueprint to resolve which concrete implementations to inject. See ek9 -h E08220 for details."}],"companions":[]}
{"id":673,"category":"DI Validation","question":"What happens when a component is injected but not registered?","url":"https://ek9.io/qa/QA0673.html","alternatePhrasings":["What is E08210 missing registration for injected component?","How do I fix unresolved injection errors?","Why does my program fail with missing component registration?"],"answer":"When a program injects a component type that has no matching registration in the linked application, the compiler raises E08210. Every injection point must have a corresponding 'register' declaration.\n\nMISSING REGISTRATION\nIf program injects 'service as Logger!' but the application has no 'register ... as Logger', the compiler detects this at compile time.\n\nCOMPLETENESS VALIDATION\nThe compiler validates that ALL injection points in the program (and its transitive component dependencies) can be satisfied by the application's registrations.\n\nFIXING\n1. Add the missing registration to the application\n2. Register a concrete implementation 'as' the abstract type\n3. Ensure ALL transitive dependencies are registered\n\nTRANSITIVE DEPENDENCIES\nIf ComponentA injects ComponentB, and ComponentB injects ComponentC, then the application must register implementations for BOTH B and C.\n\nSee Q672 for program-application linking. See Q674 for duplicate registration. See Q671 for circular dependencies.","ek9Example":"defines module qa.divalidation.missingreg\n\n  defines component\n\n    <?-\n      Abstract authentication service.\n    -?>\n    AuthService as abstract\n\n      authenticate() as abstract\n        -> credentials as String\n        <- authenticated as Boolean?\n\n      default operator ?\n\n    <?-\n      Token-based authentication implementation.\n    -?>\n    TokenAuth extends AuthService\n\n      override authenticate()\n        -> credentials as String\n        <- authenticated as Boolean: true\n\n      default operator ?\n\n    <?-\n      Abstract session manager.\n    -?>\n    SessionManager as abstract\n\n      createSession() as abstract\n        -> userName as String\n        <- sessionToken as String?\n\n      default operator ?\n\n    <?-\n      In-memory session manager implementation.\n    -?>\n    InMemorySessionManager extends SessionManager\n\n      override createSession()\n        -> userName as String\n        <- sessionToken as String: \"\"\n\n        sessionToken: \"session-\" + userName\n\n      default operator ?\n\n  defines application\n\n    <?-\n      Complete application: registers BOTH components.\n      Every injection point in the program must be satisfied.\n    -?>\n    CompleteApp\n      register TokenAuth() as AuthService\n      register InMemorySessionManager() as SessionManager\n\n  defines program\n\n    MissingRegistrationDemo() with application of CompleteApp\n      stdout <- Stdout()\n\n      authService as AuthService!\n      sessionMgr as SessionManager!\n\n      isValid <- authService.authenticate(\"admin:secret\")\n      stdout.println(`Authenticated: ${isValid}`)\n\n      token <- sessionMgr.createSession(\"admin\")\n      stdout.println(`Session: ${token}`)","migrationContext":"Java: Spring NoSuchBeanDefinitionException at runtime. Python: runtime ImportError. Go: compile-time (manual wiring). Rust: compile-time (no DI). EK9: compile-time E08210 validates all injection points have registrations.","keywords":["DI","E08210","application","compile-time","component","inject","injection","missing","registration","validate"],"primaryTopics":[],"typicalErrors":[{"error":"E50010","correct":"CompleteApp\n      register TokenAuth() as AuthService\n      register InMemorySessionManager() as SessionManager","incorrect":"IncompleteApp\n      register TokenAuth() as AuthService","explanation":"Every injection point in the program and its transitive dependencies must have a matching registration in the linked application. If the program injects SessionManager but the application only registers AuthService, the compiler cannot resolve the SessionManager injection point. See ek9 -h E50010 for details."}],"companions":[]}
{"id":674,"category":"DI Validation","question":"Why can't I register two implementations for the same abstract type?","url":"https://ek9.io/qa/QA0674.html","alternatePhrasings":["What is E08230 duplicate registration?","How do I handle multiple implementations of one interface?","Can I register the same abstract type twice?"],"answer":"Each abstract component type can have exactly ONE concrete registration per application (E08230). Duplicate registrations create ambiguity in injection resolution.\n\nWHY ONE REGISTRATION ONLY\nWhen a program injects 'service as Logger!', the compiler must resolve exactly one concrete implementation. Two registrations for Logger would make resolution ambiguous.\n\nDIFFERENT APPLICATIONS FOR DIFFERENT WIRING\nInstead of duplicate registrations, use different applications:\n  defines application\n    ProdApp\n      register FileLogger() as Logger\n  defines application\n    TestApp\n      register MockLogger() as Logger\nEach application has one Logger binding.\n\nSee Q672 for program-application linking. See Q673 for missing registration. See Q671 for circular dependencies.","ek9Example":"defines module qa.divalidation.duplicatereg\n\n  defines component\n\n    <?-\n      Abstract email service contract.\n    -?>\n    EmailService as abstract\n\n      sendEmail() as abstract\n        -> recipient as String\n\n      default operator ?\n\n    <?-\n      SMTP implementation for production.\n    -?>\n    SmtpEmailService extends EmailService\n\n      override sendEmail()\n        -> recipient as String\n        stdout <- Stdout()\n        stdout.println(`SMTP email to ${recipient}`)\n\n      default operator ?\n\n    <?-\n      Mock implementation for testing.\n    -?>\n    MockEmailService extends EmailService\n\n      override sendEmail()\n        -> recipient as String\n        stdout <- Stdout()\n        stdout.println(`Mock email to ${recipient}`)\n\n      default operator ?\n\n  defines application\n\n    <?-\n      Production application: ONE registration for EmailService.\n      Each abstract type has exactly one concrete binding.\n    -?>\n    ProductionConfig\n      register SmtpEmailService() as EmailService\n\n    <?-\n      Test application: different implementation, same abstract type.\n      Use separate applications for different wiring configurations.\n    -?>\n    TestConfig\n      register MockEmailService() as EmailService\n\n  defines program\n\n    DuplicateRegistrationDemo() with application of ProductionConfig\n      stdout <- Stdout()\n\n      emailService as EmailService!\n      emailService.sendEmail(\"user@example.com\")\n\n      stdout.println(\"One abstract type = one registration per application\")\n      stdout.println(\"Use different applications for different implementations\")","migrationContext":"Java: Spring requires @Primary or @Qualifier for multiple beans. Python: manual selection. Go: manual wiring. Rust: no DI framework. EK9: one registration per abstract type per application, use different applications for different configurations.","keywords":["DI","E08230","ambiguous","application","component","duplicate","inject","registration","validate"],"primaryTopics":[],"typicalErrors":[{"error":"E50010","correct":"ProductionConfig\n      register SmtpEmailService() as EmailService","incorrect":"BrokenConfig\n      register SmtpEmailService() as EmailService\n      register MockEmailService() as EmailService","explanation":"Each abstract component type can have exactly one concrete registration per application. Registering two implementations for EmailService in the same application creates ambiguity when the compiler tries to resolve injection points. Use separate applications for different wiring configurations. See ek9 -h E50010 for details."}],"companions":[]}
{"id":675,"category":"DI Validation","question":"How does EK9 validate transitive component dependencies?","url":"https://ek9.io/qa/QA0675.html","alternatePhrasings":["Does EK9 check nested injection chains at compile time?","How deep does DI validation go?","What if a component's dependency has unregistered dependencies?"],"answer":"EK9 validates the ENTIRE dependency graph at compile time. If Program injects ComponentA, and ComponentA injects ComponentB, then BOTH must have registrations in the application.\n\nTRANSITIVE VALIDATION\nThe compiler walks the full injection chain:\n  Program -> ServiceA -> RepositoryB -> (no more deps)\nAll three levels must have registered implementations.\n\nFAILURE AT ANY LEVEL\nIf RepositoryB has no registration, the compiler reports E08210 even though the program only directly injects ServiceA. The transitive chain is validated.\n\nCOMPLETE WIRING\n  defines application\n    FullApp\n      register ConcreteRepoB() as RepositoryB\n      register ConcreteServiceA() as ServiceA\nBoth registrations satisfy the transitive chain.\n\nSee Q673 for missing registration. See Q671 for circular dependencies. See Q666 for pure context.","ek9Example":"defines module qa.divalidation.transitive\n\n  defines component\n\n    <?-\n      Layer 1: Abstract data access.\n      Bottom of the dependency chain.\n    -?>\n    DataAccess as abstract\n\n      readRecord() as abstract\n        -> recordKey as String\n        <- recordData as String?\n\n      default operator ?\n\n    <?-\n      Concrete data access implementation.\n    -?>\n    DatabaseAccess extends DataAccess\n\n      override readRecord()\n        -> recordKey as String\n        <- recordData as String: \"\"\n\n        recordData: \"db:\" + recordKey\n\n      default operator ?\n\n    <?-\n      Layer 2: Abstract business logic.\n      Depends on DataAccess (injection).\n    -?>\n    BusinessLogic as abstract\n\n      executeRule() as abstract\n        -> ruleInput as String\n        <- ruleOutput as String?\n\n      default operator ?\n\n    <?-\n      Concrete business logic.\n      Injects DataAccess (transitive dependency).\n    -?>\n    OrderLogic extends BusinessLogic\n\n      dataAccess as DataAccess!\n\n      override executeRule()\n        -> ruleInput as String\n        <- ruleOutput as String: \"\"\n\n        rawData <- dataAccess.readRecord(ruleInput)\n        if rawData?\n          ruleOutput: \"Processed: \" + rawData\n\n      default operator ?\n\n  defines application\n\n    <?-\n      Application must register ALL levels of the dependency chain.\n      Missing any level triggers E08210.\n    -?>\n    TransitiveApp\n      register DatabaseAccess() as DataAccess\n      register OrderLogic() as BusinessLogic\n\n  defines program\n\n    TransitiveDiDemo() with application of TransitiveApp\n      stdout <- Stdout()\n\n      logic as BusinessLogic!\n      result <- logic.executeRule(\"order-123\")\n      stdout.println(result)\n\n      stdout.println(\"Transitive chain validated at compile time:\")\n      stdout.println(\"  Program -> BusinessLogic -> DataAccess\")","migrationContext":"Java: Spring validates at startup (runtime). Python: runtime discovery. Go: compile-time (manual). Rust: compile-time (no DI). EK9: full compile-time validation of transitive dependency chains.","keywords":["DI","E08210","chain","compile-time","component","dependency","inject","registration","transitive","validate","validation"],"primaryTopics":[],"typicalErrors":[{"error":"E50010","correct":"TransitiveApp\n      register DatabaseAccess() as DataAccess\n      register OrderLogic() as BusinessLogic","incorrect":"IncompleteApp\n      register OrderLogic() as BusinessLogic","explanation":"The compiler validates the entire dependency graph. OrderLogic injects DataAccess, so even though the program only directly injects BusinessLogic, the application must also register a DataAccess implementation. Missing any level in the transitive chain triggers this error. See ek9 -h E50010 for details."}],"companions":[]}
{"id":676,"category":"Override Mechanics","question":"How do override and access modifiers interact in a three-level class hierarchy?","url":"https://ek9.io/qa/QA0676.html","alternatePhrasings":["What is E05120 method signature mismatch in override?","What is E05130 access modifier mismatch in override?","What is E05140 covariant return type required in override?","How does override protected work in deep class hierarchies?"],"answer":"When overriding in multi-level class hierarchies, three rules must hold at every level: the override keyword must be present, the access modifier must be the same or wider, and the signature must match exactly. These rules apply equally to METHODS and OPERATORS.\n\nOVERRIDE ON METHODS AND OPERATORS\nThe override keyword works identically on both:\n  override describe() as pure         method override\n  override operator ? as pure         operator override\n  override operator $ as pure         operator override\nIf the parent defines it, the child must say override.\n\nOVERRIDE KEYWORD REQUIRED (E05120)\nAny method or operator that replaces a parent version MUST use 'override'. Without it, the compiler reports E05120. This applies to operator ? (inherited from base type), operator $ (if parent defines it), and all other operators.\n\nACCESS MODIFIER MATCHING (E05130)\nAn override can maintain or WIDEN access. Narrowing access (public to private) violates Liskov and triggers E05130.\n\nSIGNATURE MATCHING (E05140)\nParameter types and return types must match.\n\nTHREE-LEVEL CHAIN\nGrandparent declares the contract. Parent overrides it. Grandchild overrides it again. Each level must independently satisfy override, access, and signature rules — for both methods and operators.\n\nSee Q570 for override basics. See Q575 for deep override chains. See Q579 for trait override chains. ","ek9Example":"defines module qa.override.accesshierarchy\n\n  defines class\n\n    <?-\n      Level 1: Grandparent declares protected methods.\n      These form the contract for all descendants.\n    -?>\n    Grandparent as abstract\n\n      protected describe()\n        <- rtn as String: \"grandparent\"\n\n      protected classify() as pure abstract\n        <- rtn as String?\n\n      default operator ?\n\n    <?-\n      Level 2: Parent overrides with matching access.\n      Protected stays protected, override keyword present.\n    -?>\n    Parent extends Grandparent as open\n\n      override protected describe()\n        <- rtn as String: \"parent\"\n\n      override protected classify() as pure\n        <- rtn as String: \"parent-class\"\n\n      default operator ?\n\n    <?-\n      Level 3: Child overrides again, maintaining contract.\n      Each level independently satisfies all three rules.\n    -?>\n    Child extends Parent\n\n      override protected describe()\n        <- rtn as String: \"child\"\n\n      override protected classify() as pure\n        <- rtn as String: \"child-class\"\n\n      default operator ?\n\n  defines program\n\n    OverrideAccessHierarchyDemo()\n      stdout <- Stdout()\n\n      child <- Child()\n      stdout.println(`Override chain verified: ${child?}`)","migrationContext":"Java: @Override annotation, access can widen (private to protected), signatures must match. Kotlin: 'override' keyword mandatory, access widening allowed. Rust: no inheritance, trait impl must match. Python: no override keyword, duck typing. EK9: 'override' keyword mandatory, access must match or widen, signature must match exactly.","keywords":["E05120","E05130","E05140","abstract","access","chain","class","hierarchy","inherit","open","override","protected","signature","virtual"],"primaryTopics":[],"typicalErrors":[{"error":"E07260","correct":"override protected describe()","incorrect":"protected describe()","explanation":"Without the 'override' keyword, the compiler treats 'protected describe()' as a new method declaration that shadows the parent method, triggering E07260. See ek9 -h E07260 for details."},{"error":"E07010","correct":"override protected classify() as pure","incorrect":"override private classify() as pure","explanation":"An override must maintain or widen the access level. Narrowing from protected to private violates Liskov Substitution and triggers E07010. See ek9 -h E07010 for details."},{"error":"E05110","correct":"override protected describe()\n        <- rtn as String: \"child\"","incorrect":"override protected describe()\n        -> extra as Integer\n        <- rtn as String: \"child\"","explanation":"The override signature must match the parent exactly. Adding, removing, or changing parameter types means the method does not actually override the parent and triggers E05110. See ek9 -h E05110 for details."}],"companions":[]}
{"id":677,"category":"Type Hierarchy Constraints","question":"How does abstract implementation flow through a three-level hierarchy?","url":"https://ek9.io/qa/QA0677.html","alternatePhrasings":["What is E05070 incorrect use of this in context?","What is E05020 circular hierarchy detection?","What is E05150 purity mismatch in override?","How do abstract intermediate classes work in EK9?"],"answer":"Note: override applies to both methods and operators (e.g., override operator ? as pure, override operator $ as pure). \n\nEK9 supports multi-level abstract hierarchies where abstract classes can extend other abstract classes, deferring implementation to concrete leaf classes. Each level can implement some abstract methods and declare new ones.\n\nABSTRACT EXTENDING ABSTRACT\nAn abstract class can extend another abstract class. The intermediate abstract class may implement some inherited abstract methods while leaving others for descendants.\n\nCONCRETE LEAF REQUIREMENT\nThe concrete (non-abstract) class at the bottom of the chain MUST implement ALL remaining abstract methods from every ancestor. Missing implementations trigger compile errors.\n\nOPEN MODIFIER REQUIREMENT (E05020)\nFor a class to be extended, it must be declared 'as open' or 'as abstract'. Abstract implies open. A class not declared as open or abstract cannot be extended.\n\nCORRECT PATTERN\n  Shape as abstract           // Level 1: pure abstract\n    area() as abstract\n  Polygon extends Shape as abstract  // Level 2: still abstract\n    sides() as abstract\n    override area()            // Implements area\n  Triangle extends Polygon     // Level 3: concrete, implements sides()\n    override sides()\n\nSee Q602 for circular hierarchy detection. See Q605 for this-context rules. See Q567 for purity chain inheritance. See Q606 for method shadowing.","ek9Example":"defines module qa.typehierarchy.abstractchain\n\n  defines class\n\n    <?-\n      Level 1: Pure abstract base.\n      Declares the fundamental operations.\n    -?>\n    Shape as abstract\n\n      area() as pure abstract\n        <- rtn as Float?\n\n      perimeter() as pure abstract\n        <- rtn as Float?\n\n      describe() as pure abstract\n        <- rtn as String?\n\n      default operator ?\n\n    <?-\n      Level 2: Abstract intermediate.\n      Implements describe(), leaves area() and perimeter() abstract.\n    -?>\n    Polygon extends Shape as abstract\n\n      sides() as pure abstract\n        <- rtn as Integer?\n\n      override describe() as pure\n        <- rtn as String: \"polygon\"\n\n      default operator ?\n\n    <?-\n      Level 3: Concrete leaf.\n      Must implement ALL remaining abstract methods.\n    -?>\n    Rectangle extends Polygon\n      width <- Float()\n      height <- Float()\n\n      Rectangle()\n        ->\n          width as Float\n          height as Float\n        this.width: width\n        this.height: height\n\n      override area() as pure\n        <- rtn as Float: width * height\n\n      override perimeter() as pure\n        <- rtn as Float: (width + height) * 2.0\n\n      override sides() as pure\n        <- rtn as Integer: 4\n\n      override describe() as pure\n        <- rtn as String: \"rectangle\"\n\n      default operator ?\n\n    <?-\n      Another concrete leaf from the same hierarchy.\n    -?>\n    EquilateralTriangle extends Polygon\n      sideLength <- Float()\n\n      EquilateralTriangle()\n        -> sideLength as Float\n        this.sideLength: sideLength\n\n      override area() as pure\n        <- rtn as Float: (sideLength * sideLength * 0.433)\n\n      override perimeter() as pure\n        <- rtn as Float: sideLength * 3.0\n\n      override sides() as pure\n        <- rtn as Integer: 3\n\n      override describe() as pure\n        <- rtn as String: \"equilateral triangle\"\n\n      default operator ?\n\n    <?-\n      A concrete, non-abstract pair. Panel may extend Widget only while Widget is\n      'as open'; drop 'as open' and Widget is closed, so the extension is rejected.\n    -?>\n    Widget as open\n      label <- \"widget\"\n\n      default operator ?\n\n    Panel extends Widget\n      visible <- true\n\n      default operator ?\n\n  defines program\n\n    AbstractChainDemo()\n      stdout <- Stdout()\n\n      shapes <- List() of Shape\n      shapes += Rectangle(4.0, 3.0)\n      shapes += EquilateralTriangle(5.0)\n\n      for shape in shapes\n        stdout.println(`${shape.describe()}: area=${shape.area()}`)","migrationContext":"Java: abstract class extending abstract class is standard. Kotlin: abstract classes extend abstract classes. Python: ABC can extend ABC. Rust: no inheritance, use trait composition. Go: interface embedding instead. EK9: abstract extends abstract with 'as abstract' or 'as open', concrete leaf must implement all remaining abstract methods.","keywords":["E05020","E05070","E05150","abstract","chain","circular","concrete","hierarchy","implementation","inherit","leaf","open","type"],"primaryTopics":[],"typicalErrors":[{"error":"E05030","correct":"Widget as open","incorrect":"Widget","explanation":"EK9 classes are closed by default. Panel can extend Widget only because Widget is declared 'as open'. Removing 'as open' makes Widget closed, so 'Panel extends Widget' triggers E05030 - not open to be extended. See ek9 -h E05030 for details."},{"error":"E05110","correct":"Rectangle extends Polygon","incorrect":"Rectangle extends String","explanation":"A class can only extend a compatible type. Attempting to extend a built-in or incompatible type triggers E05110 because the target type is not valid in this context. See ek9 -h E05110 for details."},{"error":"E05150","correct":"override area() as pure\n        <- rtn as Float: width * height","incorrect":"override area()\n        <- rtn as Float: width * height","explanation":"When overriding a pure abstract method, the override must also be declared pure. Removing purity from the override creates a contract mismatch and triggers E05150. See ek9 -h E05150 for details."}],"companions":[]}
{"id":678,"category":"Purity Contracts","question":"How do pure and non-pure methods coexist in an override hierarchy?","url":"https://ek9.io/qa/QA0678.html","alternatePhrasings":["What is E05190 pure constructor mismatch?","What happens when a pure override tries to mutate state?","What happens when a pure method calls a non-pure method?","Can a class have both pure and non-pure methods with overrides?"],"answer":"A class can have both pure and non-pure methods. The purity contract applies per-method, not per-class. When overriding, pure methods must stay pure, and non-pure methods can remain non-pure.\n\nPURE METHOD OVERRIDE (E05150)\nIf a parent method is declared as pure, every override in every descendant MUST also be pure. Removing purity from an override triggers E05150.\n\nNON-PURE METHOD OVERRIDE\nIf a parent method is NOT pure, overrides are also not pure. A descendant CANNOT add purity to an override that was not pure in the parent (E05160).\n\nMUTATION IN PURE (E08120)\nPure methods cannot mutate fields. Using ':=' on a field, or '+=' on a collection, inside a pure method triggers E08120.\n\nCALLING NON-PURE FROM PURE (E08130)\nA pure method cannot call a non-pure method. This transitively ensures that pure methods have no side effects.\n\nPURE CONSTRUCTOR RULE (E05190)\nIf ANY constructor is pure, ALL constructors must be pure. Mixed constructor purity is not allowed.\n\nSee Q560 for pure method basics. See Q561 for purity override rules. See Q567 for purity chain inheritance. See Q566 for pure method restrictions.","ek9Example":"defines module qa.purity.overridecontract\n\n  defines class\n\n    <?-\n      Base class with both pure and non-pure methods.\n      Pure methods define read-only contract.\n      Non-pure methods allow state mutation.\n      Fields are always private; accessor methods expose state.\n    -?>\n    Account as open\n      balance <- Float()\n      accountName <- String()\n\n      Account()\n        ->\n          accountName as String\n          initialBalance as Float\n        this.accountName: accountName\n        this.balance: initialBalance\n\n      //Pure accessor: subclasses use this to read balance\n      getBalance() as pure\n        <- rtn as Float: balance\n\n      //Pure accessor: subclasses use this to read name\n      getAccountName() as pure\n        <- rtn as String: accountName\n\n      //Pure: read-only computation\n      formatSummary() as pure\n        <- rtn as String: `${accountName}: ${balance}`\n\n      //Non-pure: mutates balance field\n      deposit()\n        -> amount as Float\n        balance: balance + amount\n\n      //Non-pure: mutates balance field\n      withdraw()\n        -> amount as Float\n        if amount <= balance\n          balance: balance - amount\n\n      default operator ?\n\n    <?-\n      Child class: pure overrides stay pure, non-pure stay non-pure.\n      Each override maintains the parent contract.\n      Accesses parent state via accessor methods (fields are private).\n    -?>\n    SavingsAccount extends Account\n      interestRate <- Float()\n\n      SavingsAccount()\n        ->\n          accountName as String\n          initialBalance as Float\n          interestRate as Float\n        super(accountName, initialBalance)\n        this.interestRate: interestRate\n\n      //Pure override: stays pure, uses accessor methods for parent state\n      override formatSummary() as pure\n        <- rtn as String: `Savings ${getAccountName()}: ${getBalance()} @ ${interestRate}%`\n\n      //Non-pure: adds interest via parent's deposit method\n      applyInterest()\n        earned <- getBalance() * interestRate / 100.0\n        deposit(earned)\n\n      default operator ?\n\n  defines program\n\n    PurityOverrideDemo()\n      stdout <- Stdout()\n\n      savings <- SavingsAccount(\"Rainy Day\", 1000.0, 2.5)\n      stdout.println(savings.formatSummary())\n\n      savings.deposit(500.0)\n      savings.applyInterest()\n      stdout.println(savings.formatSummary())","migrationContext":"Java: no purity enforcement. Kotlin: no purity. Rust: shared references prevent mutation (different mechanism). Python: no purity. EK9: per-method purity with compile-time enforcement across the entire override chain.","keywords":["E05190","E08120","E08130","abstract","contract","function","immutable","mixed","mutation","non-pure","open","override","pure","purity","side-effect","virtual"],"primaryTopics":[],"typicalErrors":[{"error":"E05190","correct":"Account()\n        ->\n          accountName as String\n          initialBalance as Float\n        this.accountName: accountName\n        this.balance: initialBalance","incorrect":"Account() as pure\n        ->\n          accountName as String\n          initialBalance as Float\n        this.accountName :=? accountName\n        this.balance :=? initialBalance\n\n      Account()\n        -> accountName as String\n        this.accountName: accountName","explanation":"If ANY constructor is declared pure, ALL constructors in the same class must be pure. Mixed constructor purity triggers E05190. See ek9 -h E05190 for details."},{"error":"E08100","correct":"      formatSummary() as pure\n        <- rtn as String: `${accountName}: ${balance}`","incorrect":"formatSummary() as pure\n        <- rtn as String: \"\"\n        balance: balance + 1.0\n        rtn: `${accountName}: ${$balance}`","explanation":"Pure methods cannot mutate fields. Using ':=' on a field inside a pure method triggers E08100 because it constitutes a side effect. See ek9 -h E08100 for details."},{"error":"E08130","correct":"      override formatSummary() as pure\n        <- rtn as String: `Savings ${getAccountName()}: ${getBalance()} @ ${interestRate}%`","incorrect":"override formatSummary() as pure\n        <- rtn as String: \"\"\n        deposit(100.0)\n        rtn: `Savings ${getAccountName()}: ${$getBalance()} @ ${$interestRate}%`","explanation":"A pure method cannot call a non-pure method. Since deposit() mutates state, calling it from a pure method triggers E08130. See ek9 -h E08130 for details."}],"companions":[]}
{"id":679,"category":"Type Hierarchy Constraints","question":"How do sealed classes with allow only work in EK9?","url":"https://ek9.io/qa/QA0679.html","alternatePhrasings":["What is E05050 constructor delegation must be first?","What is E05100 nothing to override?","What is E05270 sealed class must be open?","How does 'allow only' restrict which classes can extend a base?"],"answer":"EK9 supports sealed class hierarchies using 'allow only'. A sealed class declares which types are permitted to extend it. This creates a closed set of subtypes, enabling exhaustive pattern matching.\n\nALLOW ONLY SYNTAX\nThe parent class lists permitted subtypes:\n  Shape allow only Circle, Rectangle as open\nOnly Circle and Rectangle may extend Shape. Any other class attempting to extend Shape is rejected.\n\nOPEN REQUIREMENT (E05270)\nThe sealed class must be declared 'as open' (via the 'allow only' syntax which implies openness). Without it, no class can extend it at all.\n\nCONSTRUCTOR DELEGATION (E05050)\nWhen child classes call super(), the super() call must be the FIRST statement in the constructor body. Code before super() triggers E05050.\n\nOVERRIDE CORRECTNESS (E05100)\nChild classes can override parent methods. Using 'override' on a method that does not exist in the parent triggers E05100.\n\nBENEFITS\nSealed hierarchies enable dispatchers to check exhaustiveness. If a dispatcher handles Shape, the compiler knows only Circle and Rectangle exist.\n\nSee Q607 for sealed class requirements. See Q609 for sealed across modules. See Q611 for hierarchy validation summary. See Q602 for circular hierarchy detection.","ek9Example":"defines module qa.typehierarchy.sealedallowonly\n\n  defines class\n\n    <?-\n      Sealed base: only Circle and Square may extend.\n      The 'allow only' creates a closed set of subtypes.\n    -?>\n    Shape allow only Circle, Square as open\n      shapeName <- String()\n\n      Shape()\n        -> shapeName as String\n        this.shapeName: shapeName\n\n      label()\n        <- rtn as String: shapeName\n\n      default operator ?\n\n    <?-\n      Permitted subtype: Circle.\n      Must call super() as FIRST statement.\n    -?>\n    Circle extends Shape\n      radius <- Float()\n\n      Circle()\n        -> radius as Float\n        super(\"circle\")\n        this.radius: radius\n\n      override label()\n        <- rtn as String: `circle r=${radius}`\n\n      default operator ?\n\n    <?-\n      Permitted subtype: Square.\n      Also calls super() first.\n    -?>\n    Square extends Shape\n      sideLength <- Float()\n\n      Square()\n        -> sideLength as Float\n        super(\"square\")\n        this.sideLength: sideLength\n\n      override label()\n        <- rtn as String: `square s=${sideLength}`\n\n      default operator ?\n\n  defines class\n\n    <?-\n      Renderer uses dispatcher to handle each sealed subtype.\n      Dispatchers are class methods, not standalone functions.\n    -?>\n    ShapeRenderer\n\n      describe() as dispatcher\n        -> shape as Shape\n        <- description as String: shape.label()\n\n      describe()\n        -> shape as Circle\n        <- description as String: `Circular: ${shape.label()}`\n\n      describe()\n        -> shape as Square\n        <- description as String: `Rectangular: ${shape.label()}`\n\n      default operator ?\n\n  defines program\n\n    SealedAllowOnlyDemo()\n      stdout <- Stdout()\n\n      renderer <- ShapeRenderer()\n\n      shapes <- List() of Shape\n      shapes += Circle(5.0)\n      shapes += Square(4.0)\n\n      for shape in shapes\n        stdout.println(renderer.describe(shape))","migrationContext":"Java: sealed classes with 'permits' (Java 17+). Kotlin: sealed classes with 'sealed' keyword. Rust: enum variants (algebraic data types). Python: no sealed classes. Go: no sealed types. EK9: 'allow only' on class declaration, compiler enforces the closed set.","keywords":["E05050","E05100","E05270","allow","circular","class","closed","exhaustive","hierarchy","inherit","only","permitted","sealed","type"],"primaryTopics":[],"typicalErrors":[{"error":"E05050","correct":"Circle()\n        -> radius as Float\n        super(\"circle\")\n        this.radius: radius","incorrect":"Circle()\n        -> radius as Float\n        this.radius: radius\n        super(\"circle\")","explanation":"When a child class calls super(), the super() call must be the first statement in the constructor body. Placing code before super() triggers E05050. See ek9 -h E05050 for details."},{"error":"E05110","correct":"      override label()\n        <- rtn as String: `circle r=${radius}`","incorrect":"override nonExistentMethod()\n        <- rtn as String: \"oops\"","explanation":"Using 'override' on a method that does not exist in the parent type triggers E05110. The method name must match an actual parent method. See ek9 -h E05110 for details."},{"error":"E05270","correct":"Shape allow only Circle, Square as open","incorrect":"Shape allow only Circle, Square","explanation":"A sealed class using 'allow only' must be declared 'as open' so that the permitted subtypes can extend it. Without 'as open', no class can extend it at all, which contradicts the 'allow only' declaration and triggers E05270. See ek9 -h E05270 for details."}],"companions":[]}
{"id":680,"category":"Type Hierarchy Constraints","question":"How does EK9 resolve the diamond problem with trait method conflicts?","url":"https://ek9.io/qa/QA0680.html","alternatePhrasings":["What is E05210 dispatcher unreachable handler?","What is E05220 method conflict from multiple traits?","What is E05230 unresolved trait method conflict?","How do I resolve conflicting methods from two traits?"],"answer":"When a class implements two traits that declare the same method name, EK9 detects the conflict and requires the class to provide an explicit override to resolve it.\n\nDIAMOND DETECTION (E05220)\nIf TraitA declares process() and TraitB also declares process(), a class implementing both has a conflict. The compiler detects this and reports E05220 unless the class provides an override.\n\nRESOLUTION VIA OVERRIDE (E05230)\nThe class must provide its own 'override' of the conflicting method. This removes ambiguity by giving the class full control over the method body. Without the override, the compiler cannot choose between the two trait implementations and reports E05230.\n\nUNREACHABLE DISPATCH (E05210)\nIn dispatchers, a handler for a type that is not reachable from the base dispatch type is flagged with E05210. This ensures all dispatcher handlers can actually be invoked.\n\nCORRECT PATTERN\n  TraitA\n    process() ...\n  TraitB\n    process() ...\n  MyClass with trait of TraitA, TraitB\n    override process() ...   // Resolves the conflict\n\nSee Q107 for multiple trait implementation. See Q579 for trait override chains. See Q611 for hierarchy validation summary.","ek9Example":"defines module qa.typehierarchy.diamondtrait\n\n  defines trait\n\n    <?-\n      First trait: declares a process() method with default.\n    -?>\n    Auditable\n      auditEntry() as pure\n        <- rtn as String: \"audited\"\n\n      formatRecord() as pure\n        <- rtn as String: `[AUDIT] ${auditEntry()}`\n\n    <?-\n      Second trait: declares a formatRecord() method too.\n      This creates a conflict for classes implementing both traits.\n    -?>\n    Printable\n      printLabel() as pure\n        <- rtn as String: \"printable\"\n\n      formatRecord() as pure\n        <- rtn as String: `[PRINT] ${printLabel()}`\n\n  defines class\n\n    <?-\n      Class implementing both traits.\n      Must override formatRecord() to resolve the diamond conflict.\n    -?>\n    DocumentRecord with trait of Auditable, Printable\n      title <- String()\n\n      DocumentRecord()\n        -> title as String\n        this.title: title\n\n      override auditEntry() as pure\n        <- rtn as String: title\n\n      override printLabel() as pure\n        <- rtn as String: title\n\n      //Resolves the diamond: both Auditable and Printable have formatRecord()\n      override formatRecord() as pure\n        <- rtn as String: `[DOC] ${title}`\n\n      default operator ?\n\n    <?-\n      Another class implementing only one trait: no conflict.\n    -?>\n    SimpleAudit with trait of Auditable\n      action <- String()\n\n      SimpleAudit()\n        -> action as String\n        this.action: action\n\n      override auditEntry() as pure\n        <- rtn as String: action\n\n      default operator ?\n\n  defines program\n\n    DiamondTraitDemo()\n      stdout <- Stdout()\n\n      doc <- DocumentRecord(\"Invoice #42\")\n      stdout.println(doc.formatRecord())\n      stdout.println(doc.auditEntry())\n      stdout.println(doc.printLabel())\n\n      simple <- SimpleAudit(\"login\")\n      stdout.println(simple.formatRecord())","migrationContext":"Java: interface default method conflict requires class override. Kotlin: must override and can use super<TraitName>.method(). Python: MRO determines which implementation wins. C++: virtual inheritance for diamond. Rust: explicit disambiguation with <Type as Trait>::method(). EK9: class must provide override for conflicting trait methods.","keywords":["E05210","E05220","E05230","abstract","circular","conflict","diamond","function","hierarchy","inherit","multiple","open","override","resolution","trait","type","virtual"],"primaryTopics":["diamond problem","diamond inheritance","multiple inheritance conflict"],"typicalErrors":[{"error":"E50010","correct":"DocumentRecord with trait of Auditable, Printable","incorrect":"DocumentRecord with trait of Auditable, Printable, UnrelatedTrait","explanation":"UnrelatedTrait is not defined in this module. Adding an unresolved type to the trait list triggers E50010. See ek9 -h E50010 for details."},{"error":"E06150","correct":"override formatRecord() as pure\n        <- rtn as String: `[DOC] ${title}`","incorrect":"//formatRecord() not overridden, leaving the diamond conflict unresolved","explanation":"When two traits provide conflicting method implementations and the implementing class does not override the method to resolve the ambiguity, E06150 is reported. The class must provide its own override. See ek9 -h E06150 for details."}],"companions":[]}
{"id":681,"category":"Generics","question":"What is the correct syntax for generic type parameterization and instantiation?","url":"https://ek9.io/qa/QA0681.html","alternatePhrasings":["What is E06200 generic type reference error?","What is E06210 generic type instantiation error?","What is E06220 missing generic type parameter?","How do I correctly reference and create generic types?"],"answer":"EK9 uses 'of type T' syntax for declaring generic type parameters and 'of TypeName' for instantiating them. Correct usage requires matching the number of type parameters.\n\nDECLARATION SYNTAX\nGeneric type parameters use 'of type':\n  Container of type T\n  Mapping of type (K, V)\nThe parameter names become placeholders for concrete types.\n\nINSTANTIATION SYNTAX\nConcrete types replace parameters with 'of':\n  Container of String\n  Mapping of (String, Integer)\nThe number of concrete types must match the declared parameters.\n\nMISSING PARAMETERS (E06220)\nOmitting required type parameters triggers E06220:\n  container <- Container()          // Needs type unless inferred from args\n  container <- Container() of String // Correct: explicit type\n\nINFERRENCE FROM ARGUMENTS\nWhen constructor arguments match type parameters, inference works:\n  container <- Container(\"hello\")   // T inferred as String\n\nSee Q642 for constructor inference. See Q654 for parameter count validation. See Q656 for argument count validation.","ek9Example":"defines module qa.genericsdeep.parameterizationsyntax\n\n  defines class\n\n    <?-\n      Single type parameter generic.\n      Demonstrates correct 'of type T' declaration syntax.\n    -?>\n    Holder of type T\n      content as T?\n\n      Holder() as pure\n        content :=? T()\n\n      Holder() as pure\n        -> initialContent as T\n        content :=? initialContent\n\n      getContent()\n        <- rtn as T: T()\n        rtn :=? T(content)\n\n      hasContent() as pure\n        <- rtn as Boolean: content?\n\n      default operator ?\n\n    <?-\n      Multi-parameter generic.\n      Both parameters must be specified at instantiation.\n    -?>\n    Pair of type (A, B)\n      first as A?\n      second as B?\n\n      Pair() as pure\n        first :=? A()\n        second :=? B()\n\n      Pair() as pure\n        ->\n          first as A\n          second as B\n        this.first :=? first\n        this.second :=? second\n\n      getFirst()\n        <- rtn as A: A()\n        rtn :=? A(first)\n\n      getSecond()\n        <- rtn as B: B()\n        rtn :=? B(second)\n\n      default operator ?\n\n  defines program\n\n    GenericParameterizationDemo()\n      stdout <- Stdout()\n\n      //Inference from single argument\n      intHolder <- Holder(42)\n      stdout.println(`Integer holder set: ${intHolder?}`)\n\n      //Type declaration (no parentheses on lhs)\n      typedHolder as Holder of String: Holder(\"typed\")\n      stdout.println(`Typed holder: ${typedHolder?}`)\n\n      //Explicit type parameter (parentheses required for constructor)\n      emptyHolder <- Holder() of String\n      stdout.println(`Empty holder set: ${emptyHolder?}`)\n\n      //Inference from multiple arguments\n      entry <- Pair(\"key\", 100)\n      stdout.println(`Pair set: ${entry?}`)\n\n      //Explicit multi-parameter\n      emptyPair <- Pair() of (Float, Boolean)\n      stdout.println(`Empty pair set: ${emptyPair?}`)","migrationContext":"Java: angle brackets List<String>. Rust: turbofish Vec::<String>. Go: brackets List[String]. Python: hints List[str]. EK9: 'of' syntax - List of String, Dict of (String, Integer).","keywords":["E06200","E06210","E06220","generic","instantiation","of","parameterization","syntax","type","type-parameter"],"primaryTopics":[],"typicalErrors":[{"error":"E50060","correct":"stdout.println(`Integer holder set: ${intHolder?}`)","incorrect":"stdout.println(intHolder.toString())","explanation":"Holder has no toString() method. Use the $ prefix operator or ? for boolean check. See ek9 -h E50060 for details."},{"error":"E06010","correct":"intHolder <- Holder(42)","incorrect":"intHolder <- Holder()(42)","explanation":"Generic type instantiation must follow the correct syntax. Using incorrect call syntax when creating a parameterised type triggers E06010. See ek9 -h E06010 for details."},{"error":"E06010","correct":"emptyHolder <- Holder() of String","incorrect":"emptyHolder <- Holder()","explanation":"When no constructor arguments allow type inference, the type parameter must be explicitly specified. Omitting it triggers E06010 because the compiler cannot determine T. See ek9 -h E06010 for details."},{"error":"E06200","correct":"      typedHolder as Holder of String: Holder(\"typed\")","incorrect":"      typedHolder as Holder() of String: Holder(\"typed\")","explanation":"Parentheses are not allowed in type declarations on the left-hand side. Use 'Holder of String' not 'Holder() of String' for type references. See ek9 -h E06200 for details."},{"error":"E06210","correct":"      emptyHolder <- Holder() of String","incorrect":"      emptyHolder <- Holder of String","explanation":"Parentheses are required when constructing a generic type. Use 'Holder() of String' not 'Holder of String' for constructors. See ek9 -h E06210 for details."}],"companions":[]}
{"id":682,"category":"Generics","question":"How does field encapsulation work in generic classes?","url":"https://ek9.io/qa/QA0682.html","alternatePhrasings":["What is E06180 private field not accessible?","Can I access generic class fields from outside?","How do I expose generic type state safely?"],"answer":"Generic class fields follow the same access rules as non-generic classes: fields are always private. Public methods provide controlled access to internal state.\n\nPRIVATE BY DEFAULT\nGeneric class fields are private. Accessing them from outside the class triggers E06180:\n  box <- Box(42)\n  box.content       // ERROR E06180: private field\n  box.getContent()  // OK: public method\n\nPUBLIC API METHODS\nProvide accessor methods to expose state safely:\n  getContent() as pure\n    <- rtn as T: content\nThis allows reading without exposing the field directly.\n\nENCAPSULATION BENEFIT\nGeneric types can validate or transform values without exposing internals. The type parameter T flows through the public API but the storage is private.\n\nDEFAULT OPERATORS\nThe 'default operator ?' and other default operators work with private fields because they are generated within the class scope.\n\nSee Q645 for public constructor requirement. See Q642 for constructor inference. See Q651 for constraint constructor match.","ek9Example":"defines module qa.genericsdeep.encapsulation\n\n  defines class\n\n    <?-\n      Generic class with proper encapsulation.\n      Fields are private, public methods provide access.\n    -?>\n    SafeBox of type T\n      stored as T?\n\n      SafeBox() as pure\n        stored :=? T()\n\n      SafeBox() as pure\n        -> initialValue as T\n        stored :=? initialValue\n\n      //Public accessor: safe read access\n      retrieve()\n        <- rtn as T: T()\n        if stored?\n          rtn: T(stored)\n\n      //Public check: is content available?\n      hasValue() as pure\n        <- rtn as Boolean: stored?\n\n      //Public mutator: controlled write access\n      store()\n        -> newValue as T\n        stored: newValue\n\n      default operator ?\n\n    <?-\n      Generic wrapper adding validation logic.\n      Internal state fully encapsulated.\n    -?>\n    ValidatedContainer of type T\n      content as T?\n      validationCount as Integer: Integer()\n\n      ValidatedContainer() as pure\n        content :=? T()\n\n      ValidatedContainer() as pure\n        -> initialContent as T\n        content :=? initialContent\n\n      //Public: access with side effect tracking\n      access()\n        <- rtn as T: T()\n        if content?\n          validationCount: validationCount + 1\n          rtn: T(content)\n\n      //Public: how many times has content been accessed?\n      accessCount() as pure\n        <- rtn as Integer: validationCount\n\n      default operator ?\n\n  defines program\n\n    GenericEncapsulationDemo()\n      stdout <- Stdout()\n\n      box <- SafeBox(42)\n      stdout.println(`Has value: ${box.hasValue()}`)\n\n      retrieved <- box.retrieve()\n      stdout.println(`Retrieved: ${retrieved}`)\n\n      tracked <- ValidatedContainer(\"important\")\n      firstAccess <- tracked.access()\n      stdout.println(`First: ${firstAccess}`)\n      secondAccess <- tracked.access()\n      stdout.println(`Second: ${secondAccess}`)\n      stdout.println(`Accessed ${tracked.accessCount()} times`)","migrationContext":"Java: generic fields can be protected or package-private. C++: template fields follow standard access control. Kotlin: generic properties follow standard access. Rust: struct fields can be pub. EK9: all fields private in classes (including generic classes), use methods for access.","keywords":["API","E06180","accessor","class","encapsulation","field","generic","method","private","type-parameter"],"primaryTopics":[],"typicalErrors":[{"error":"E06180","correct":"retrieved <- box.retrieve()","incorrect":"retrieved <- box.stored","explanation":"Fields in generic classes (and all EK9 classes) are always private. Accessing the 'stored' field directly from outside the class triggers E06180. Use public accessor methods like retrieve() instead. See ek9 -h E06180 for details."}],"companions":[]}
{"id":683,"category":"Generics","question":"What are the complete constructor rules for generic types?","url":"https://ek9.io/qa/QA0683.html","alternatePhrasings":["What is E06040 generic constructor argument type mismatch?","What is E06050 generic default constructor required?","What is E06060 generic constructor must be public?","What constructors does a generic type need?"],"answer":"Generic types have specific constructor requirements to support type inference, default instantiation, and external usage.\n\nTWO CONSTRUCTORS REQUIRED (E06040)\nEvery generic type must have a no-argument constructor and an inferred (typed) constructor. A conceptual-T property declared 'item as T?' must NOT be left null by the no-arg constructor - give it an unset 'T()' so it is present-but-unset, never null:\n  Container of type T\n    item as T?\n    Container() as pure\n      item :=? T()\n\nPARAMETER TYPE MATCHING (E06050)\nConstructor parameters must use the declared type parameters:\n  Box of type T\n    Box(item as T)        // Correct: parameter is T\n    Box(item as String)   // Wrong: must use T\n\nACCESS (E06060)\nThe inferred (parameterised) constructor must be public for type inference. The no-arg DEFAULT constructor may be 'default private' as an escape hatch when the field cannot be given an unset 'T()' (T abstract, holds a function, or has no public no-arg constructor); external code then cannot create an empty instance. Protected is never allowed.\n\nPURE CONSTRUCTOR CONSISTENCY\nIf the default constructor is pure, all other constructors must also be pure (E05190). In a pure constructor ':=' is forbidden, so use the guarded ':=?' to first-initialise the field.\n\nCOMBINED PATTERN\n  Container of type T\n    item as T?\n    Container() as pure\n      item :=? T()                   // optional field, never null\n    Container(item as T) as pure     // inferred, public\n      this.item :=? item\n\nSee Q642 for constructor inference. See Q643 for two-constructor requirement. See Q645 for the private no-arg escape hatch.","ek9Example":"defines module qa.genericsdeep.constructorrules\n\n  defines class\n\n    <?-\n      Correct generic type with all constructor rules satisfied.\n      1. Default constructor present (public, pure)\n      2. Typed constructor uses type parameter T\n      3. All constructors are pure (consistency)\n    -?>\n    Container of type T\n      element as T?\n\n      //Rule 1: default no-arg constructor required\n      Container() as pure\n        element :=? T()\n\n      //Rule 2: typed constructor uses T (not a concrete type)\n      Container() as pure\n        -> item as T\n        element :=? item\n\n      //Public methods for access\n      getElement()\n        <- rtn as T: T()\n        rtn :=? T(element)\n\n      isEmpty() as pure\n        <- rtn as Boolean: not element?\n\n      default operator ?\n\n    <?-\n      Multi-parameter generic with correct constructors.\n      Both type parameters used in typed constructor.\n    -?>\n    Association of type (K, V)\n      theKey as K?\n      theValue as V?\n\n      //Default no-arg constructor\n      Association() as pure\n        theKey :=? K()\n        theValue :=? V()\n\n      //Typed constructor uses both K and V\n      Association() as pure\n        ->\n          theKey as K\n          theValue as V\n        this.theKey :=? theKey\n        this.theValue :=? theValue\n\n      key()\n        <- rtn as K: K()\n        rtn :=? K(theKey)\n\n      getTheValue()\n        <- rtn as V: V()\n        rtn :=? V(theValue)\n\n      default operator ?\n\n  defines program\n\n    GenericConstructorRulesDemo()\n      stdout <- Stdout()\n\n      //Type inference from argument\n      intContainer <- Container(42)\n      stdout.println(`Container set: ${intContainer?}`)\n\n      //Explicit type parameter\n      emptyContainer <- Container() of String\n      stdout.println(`Empty container: ${emptyContainer.isEmpty()}`)\n\n      //Multi-parameter inference\n      assoc <- Association(\"key\", 99)\n      stdout.println(`Association set: ${assoc?}`)","migrationContext":"Java: generic classes need no special constructor rules. Rust: no constructors, uses associated functions. Go: no constructors, uses factory functions. Kotlin: generic classes follow standard constructor rules. EK9: generic types require default public constructor, typed constructor params must match type parameters.","keywords":["E06040","E06050","E06060","constant","constructor","default","generic","immutable","parameter","pure","side-effect","type","type-parameter"],"primaryTopics":[],"typicalErrors":[{"error":"E06050","correct":"Container() as pure\n        -> item as T\n        element :=? item","incorrect":"Container() as pure\n        -> item as String\n        element :=? item","explanation":"Constructor parameters in generic types must use the declared type parameter T, not a concrete type like String. Using a concrete type triggers E06050. See ek9 -h E06050 for details."},{"error":"E06040","correct":"      Container() as pure\n        element :=? T()\n\n      //Rule 2: typed constructor uses T (not a concrete type)\n      Container() as pure\n        -> item as T","incorrect":"      Container() as pure\n        -> item as T","explanation":"Every generic type needs two constructors, a default no-argument one and an inferred typed one; omitting the no-arg constructor triggers this. See ek9 -h E06040 for details."},{"error":"E50060","correct":"stdout.println(`Container set: ${intContainer?}`)","incorrect":"stdout.println(intContainer.toString())","explanation":"Container has no toString() method. Use the $ prefix operator or ? for boolean check. See ek9 -h E50060 for details."}],"companions":[]}
{"id":684,"category":"Web Services","question":"What are the rules for valid service URI paths and method mapping?","url":"https://ek9.io/qa/QA0684.html","alternatePhrasings":["What is E07790 invalid service URI path?","What is E07800 duplicate service endpoint?","What is E07820 path parameter not in method signature?","What is E07860 service method parameter error?"],"answer":"EK9 services map URI paths to methods and operators. Several rules govern valid service definitions to prevent runtime routing errors.\n\nVALID URI PATHS (E07790)\nService paths must follow URI syntax. The base path is declared on the service name, and each method adds a relative path:\n  ProductService :/products\n    listAll() as GET for :/all\nInvalid paths (missing colon, empty segments) trigger E07790.\n\nNO DUPLICATE ENDPOINTS (E07800)\nTwo methods or operators cannot map to the same HTTP verb and path combination. Each endpoint must be unique within the service.\n\nPATH PARAMETER MATCHING (E07820)\nPath parameters like '/{productId}' must have a corresponding method parameter:\n  findById() as GET for :/{productId}\n    -> productId as String\nMissing the parameter in the method signature triggers E07820.\n\nMETHOD PARAMETER RULES (E07860)\nService method parameters have specific type requirements. Path parameters must be String type.\n\nSee Q657 for URI mapping basics. See Q658 for path parameter binding. See Q659 for service return types.","ek9Example":"defines module qa.webdeep.uripaths\n\n  defines constant\n\n    JSON_CONTENT_TYPE <- \"application/json\"\n    ENGLISH_LANGUAGE <- \"en\"\n\n  defines service\n\n    <?-\n      Service demonstrating valid URI paths with distinct endpoints.\n      Base path plus method-level paths, all unique.\n    -?>\n    ProductService :/products open\n\n      //GET /products/featured — no path params\n      featured() as GET for :/featured\n        <- response as HTTPResponse: () with trait HTTPResponse\n          override content()\n            <- rtn as String: `[\"featured-item\"]`\n          override status() as pure\n            <- rtn as Integer: 200\n          override contentType() as pure\n            <- rtn as String: JSON_CONTENT_TYPE\n          override cacheControl() as pure\n            <- rtn as String: \"max-age=120\"\n          override contentLanguage() as pure\n            <- rtn as String: ENGLISH_LANGUAGE\n          default operator ?\n\n      //GET /products/{productId} — path param matches method param\n      findById() as GET for :/{productId}\n        -> productId as String\n        <- response as HTTPResponse: (capturedId: productId) with trait HTTPResponse\n          override content()\n            <- rtn as String: `{\"id\": \"${capturedId}\"}`\n          override status() as pure\n            <- rtn as Integer: 200\n          override contentType() as pure\n            <- rtn as String: JSON_CONTENT_TYPE\n          override cacheControl() as pure\n            <- rtn as String: \"no-cache\"\n          override contentLanguage() as pure\n            <- rtn as String: ENGLISH_LANGUAGE\n          default operator ?\n\n      //GET /products/{categoryName}/items — different path, no conflict\n      byCategory() as GET for :/{categoryName}/items\n        -> categoryName as String\n        <- response as HTTPResponse: (capturedCategory: categoryName) with trait HTTPResponse\n          override content()\n            <- rtn as String: `{\"category\": \"${capturedCategory}\"}`\n          override status() as pure\n            <- rtn as Integer: 200\n          override contentType() as pure\n            <- rtn as String: JSON_CONTENT_TYPE\n          override cacheControl() as pure\n            <- rtn as String: \"max-age=60\"\n          override contentLanguage() as pure\n            <- rtn as String: ENGLISH_LANGUAGE\n          default operator ?\n\n  defines application\n\n    ProductApp\n      register ProductService()\n\n  defines program\n\n    ServiceUriPathsDemo()\n      stdout <- Stdout()\n      stdout.println(\"Service URI paths:\")\n      stdout.println(\"  GET /products/featured\")\n      stdout.println(\"  GET /products/{productId}\")\n      stdout.println(\"  GET /products/{categoryName}/items\")","migrationContext":"Java: @RequestMapping with @PathVariable annotations. Python: Flask @app.route('/path/<param>'). Go: gorilla/mux with {param} syntax. Rust: Actix web::resource().route(). EK9: service-level :/path syntax with compile-time validation of path-to-parameter binding.","keywords":["E07790","E07800","E07820","E07860","REST","URI","dict","duplicate","endpoint","function","http","path","service"],"primaryTopics":[],"typicalErrors":[{"error":"E50001","correct":"stdout.println(\"Service URI paths:\")","incorrect":"stdout.println(getUriPaths())","explanation":"The function getUriPaths is not defined in this module. See ek9 -h E50001 for details."},{"error":"E50001","correct":"stdout.println(\"  GET /products/featured\")","incorrect":"stdout.println(getFeaturedPath())","explanation":"The function getFeaturedPath is not defined in this module. See ek9 -h E50001 for details."}],"companions":[]}
{"id":685,"category":"Web Services","question":"What must service method bodies contain and what is prohibited?","url":"https://ek9.io/qa/QA0685.html","alternatePhrasings":["What is E07870 service method body requirement?","What is E07880 prohibited operation in service method?","Can service methods use stream operations?","What restrictions apply to service method implementations?"],"answer":"EK9 service methods have specific body requirements. Methods must have concrete implementations (not abstract), and certain operations are restricted.\n\nCONCRETE BODIES REQUIRED (E07870)\nEvery service method and operator must have a body. Abstract methods are not allowed in services because the runtime must be able to invoke every endpoint.\n\nRETURN TYPE\nService methods return HTTPResponse. The response is typically created as a dynamic class implementing the HTTPResponse trait.\n\nHTTPRESPONSE PATTERN\nThe standard pattern uses a dynamic class with trait implementation:\n  <- response as HTTPResponse: () with trait HTTPResponse\n    override content() ...\n    override status() ...\n    override contentType() ...\n\nOPERATOR ENDPOINTS\nServices can use operators (+=, -=, :~:, :^:) for CRUD:\n  operator += for POST (create)\n  operator -= for DELETE\n  operator :~: for PATCH (merge)\n  operator :^: for PUT (replace)\n\nSee Q657 for URI mapping. See Q660 for CRUD operator pattern. See Q684 for URI path rules.","ek9Example":"defines module qa.webdeep.methodbodies\n\n  defines service\n\n    <?-\n      Service with fully implemented method bodies.\n      Every method returns a concrete HTTPResponse.\n    -?>\n    StatusService :/status open\n\n      //GET /status/health — health check endpoint\n      health() as GET for :/health\n        <- response as HTTPResponse: () with trait HTTPResponse\n          override content()\n            <- rtn as String: `{\"status\": \"healthy\"}`\n          override status() as pure\n            <- rtn as Integer: 200\n          override contentType() as pure\n            <- rtn as String: \"application/json\"\n          override cacheControl() as pure\n            <- rtn as String: \"no-cache\"\n          override contentLanguage() as pure\n            <- rtn as String: \"en\"\n          default operator ?\n\n      //GET /status/version — version info endpoint\n      version() as GET for :/version\n        <- response as HTTPResponse: () with trait HTTPResponse\n          override content()\n            <- rtn as String: `{\"version\": \"1.0.0\"}`\n          override status() as pure\n            <- rtn as Integer: 200\n          override contentType() as pure\n            <- rtn as String: \"application/json\"\n          override cacheControl() as pure\n            <- rtn as String: \"max-age=300\"\n          override contentLanguage() as pure\n            <- rtn as String: \"en\"\n          default operator ?\n\n  defines application\n\n    StatusApp\n      register StatusService()\n\n  defines program\n\n    ServiceMethodBodiesDemo()\n      stdout <- Stdout()\n      stdout.println(\"Service method bodies:\")\n      stdout.println(\"  GET /status/health -> concrete body with HTTPResponse\")\n      stdout.println(\"  GET /status/version -> concrete body with HTTPResponse\")","migrationContext":"Java: Spring @RestController methods must have bodies. Python: Flask route handlers need implementations. Go: handler functions must be concrete. Rust: handler functions must be concrete. EK9: service methods must have concrete bodies, return HTTPResponse.","keywords":["E07870","E07880","HTTPResponse","abstract","body","concrete","http","method","service"],"primaryTopics":[],"typicalErrors":[{"error":"E50001","correct":"stdout.println(\"Service method bodies:\")","incorrect":"stdout.println(getMethodInfo())","explanation":"The function getMethodInfo is not defined in this module. See ek9 -h E50001 for details."},{"error":"E50001","correct":"stdout.println(\"  GET /status/health -> concrete body with HTTPResponse\")","incorrect":"stdout.println(getHealthInfo())","explanation":"The function getHealthInfo is not defined in this module. See ek9 -h E50001 for details."}],"companions":[]}
{"id":686,"category":"Security and Sanitization","question":"How must sanitized modifiers match between parent and override methods?","url":"https://ek9.io/qa/QA0686.html","alternatePhrasings":["What is E07930 sanitized parameter error?","Why must parent and child sanitized modifiers agree?","Must sanitized match exactly when overriding a method?","What happens if I remove sanitized from an override?"],"answer":"The 'sanitized' modifier on parameters must match EXACTLY between a parent method and its override. This prevents security gaps caused by polymorphic dispatch.\n\nEXACT MATCH REQUIRED (E07940)\nIf the parent declares a parameter as sanitized, the override must also declare it as sanitized. Removing sanitized from an override creates a vulnerability: callers expecting sanitization would get unsanitized processing through polymorphism.\n\nADDING SANITIZED ALSO PROHIBITED (E07930)\nIf the parent does NOT use sanitized, the override cannot add it. Adding sanitized changes the method contract (data gets modified before use), violating Liskov Substitution.\n\nWHY THIS MATTERS\n  handler <- getHandler()        // Could be Base or Derived\n  handler.process(userInput)     // Is input sanitized or not?\nWithout matching, security depends on which runtime type is active.\n\nSAFE COPY PATTERN\nInside methods with sanitized parameters, create a safe copy before processing:\n  safeCopy <- String(inputText)\n  result: transform(safeCopy)\n\nSee Q663 for override sanitized match details. See Q661 for sanitized parameter basics. See Q662 for safe copy patterns.","ek9Example":"defines module qa.sanitizeddeep.overridematch\n\n  defines class\n\n    <?-\n      Base class with sanitized parameter.\n      All overrides must maintain the sanitized modifier.\n    -?>\n    InputHandler as open\n\n      processInput()\n        -> userInput as sanitized String\n        <- cleanResult as String: \"\"\n\n        safeCopy <- String(userInput)\n        cleanResult: \"Handled: \" + safeCopy\n\n      default operator ?\n\n    <?-\n      Correct override: sanitized matches the parent.\n      Security contract is maintained through polymorphism.\n    -?>\n    StrictHandler extends InputHandler\n\n      override processInput()\n        -> userInput as sanitized String\n        <- cleanResult as String: \"\"\n\n        safeCopy <- String(userInput)\n        cleanResult: \"Strict: \" + safeCopy\n\n      default operator ?\n\n    <?-\n      Second correct override demonstrating consistent sanitized.\n    -?>\n    LoggingHandler extends InputHandler\n\n      override processInput()\n        -> userInput as sanitized String\n        <- cleanResult as String: \"\"\n\n        safeCopy <- String(userInput)\n        cleanResult: \"Logged: \" + safeCopy\n\n      default operator ?\n\n  defines program\n\n    SanitizedOverrideMatchDemo()\n      stdout <- Stdout()\n\n      handlers <- List() of InputHandler\n      handlers += StrictHandler()\n      handlers += LoggingHandler()\n\n      for handler in handlers\n        result <- handler.processInput(\"user <script> input\")\n        stdout.println(result)","migrationContext":"Java: no compiler enforcement, overrides can change validation. Python: no enforcement. Go: no inheritance. Rust: trait implementations must match. Kotlin: no sanitized concept. EK9: compiler enforces sanitized matching in overrides for Liskov compliance.","keywords":["E07930","E07940","LSP","Liskov","abstract","function","handler","match","open","override","polymorphism","sanitize","sanitized","sealed","security","validate","virtual","visitor"],"primaryTopics":[],"typicalErrors":[{"error":"E07930","correct":"safeCopy <- String(userInput)","incorrect":"safeCopy <- userInput","explanation":"Direct assignment from a sanitized parameter creates hidden aliasing where both variables share the same sanitized copy. Use String(param) copy constructor for an independent copy. See ek9 -h E07930 for details."},{"error":"E50060","correct":"result <- handler.processInput(\"user <script> input\")","incorrect":"result <- handler.processInput(\"user <script> input\").toUpperCase()","explanation":"String has no toUpperCase() method in EK9. Use upperCase() instead. See ek9 -h E50060 for details."}],"companions":[]}
{"id":687,"category":"Data Flow Safety","question":"How should Result values be accessed safely using guard expressions?","url":"https://ek9.io/qa/QA0687.html","alternatePhrasings":["What is E08030 result value accessed outside guard?","What is E08040 result guard reassignment?","How do I safely unwrap a Result in EK9?","What is the correct pattern for Result guard access?"],"answer":"Result values must be accessed through guard expressions that ensure the success or error case is properly checked before use.\n\nGUARD ACCESS PATTERN (E08030)\nThe correct way to access a Result is through a guard:\n  if result.isOk()\n    okValue <- result.ok()\n    process(okValue)\nAccessing the result value outside a guard risks using an error value as if it were a success, triggering E08030.\n\nSAFE DEFAULT PATTERN\nThe simplest approach uses okOrDefault:\n  safeValue <- result.okOrDefault(fallback)\nThis always returns a usable value without risk.\n\nNO REASSIGNMENT IN GUARD (E08040)\nOnce a guard variable is bound, it cannot be reassigned within the guard block. The variable is a one-time extraction.\n\nSee Q634 for guard expressions. See Q636 for chained guards. See Q306 for Result type basics.","ek9Example":"defines module qa.dataflow.resultguard\n\n  defines constant\n\n    DEFAULT_AGE <- 0\n    UNKNOWN_LABEL <- \"unknown\"\n\n  defines function\n\n    <?-\n      Function returning a Result.\n      Demonstrates producing success and error cases.\n    -?>\n    parseAge() as pure\n      -> ageText as String\n      <- parsed as Result of (Integer, String): Result(Integer(), \"no input\")\n\n      if ageText?\n        parsed: Result(Integer(ageText), String())\n\n    <?-\n      Correct: use okOrDefault for simple safe access.\n      No guard needed when a default is acceptable.\n    -?>\n    getAgeOrDefault() as pure\n      -> ageText as String\n      <- ageValue as Integer: DEFAULT_AGE\n\n      result <- parseAge(ageText)\n      ageValue: result.okOrDefault(DEFAULT_AGE)\n\n    <?-\n      Correct: use isOk() guard before accessing ok().\n      Check and access in the same syntactic scope.\n    -?>\n    describeAge()\n      -> ageText as String\n      <- description as String: UNKNOWN_LABEL\n\n      result <- parseAge(ageText)\n\n      if result.isOk()\n        actualAge <- result.ok()\n        description: `Age: ${actualAge}`\n\n  defines program\n\n    ResultGuardAccessDemo()\n      stdout <- Stdout()\n\n      stdout.println(`Default: ${getAgeOrDefault(\"25\")}`)\n      stdout.println(`No input: ${getAgeOrDefault(\"\")}`)\n\n      stdout.println(describeAge(\"30\"))\n      stdout.println(describeAge(\"\"))","migrationContext":"Java: Optional.ifPresent() or manual null checks. Rust: match on Result with Ok/Err. Go: explicit error return values (val, err). Python: try/except. EK9: guard expressions with isOk()/isError() or okOrDefault() with compile-time enforcement.","keywords":["E08030","E08040","access","data-flow","error","guard","initialize","isset","null-safe","ok","result","safe","safety","unwrap"],"primaryTopics":[],"typicalErrors":[{"error":"E50060","correct":"stdout.println(describeAge(\"30\"))","incorrect":"stdout.println(describeAge(\"30\").toUpperCase())","explanation":"String has no toUpperCase() method in EK9. Use upperCase() instead. See ek9 -h E50060 for details."},{"error":"E50060","correct":"ageValue: result.okOrDefault(DEFAULT_AGE)","incorrect":"ageValue: result.okOrDefault(DEFAULT_AGE).intValue()","explanation":"Integer has no intValue() method in EK9. Integer values are used directly. See ek9 -h E50060 for details."}],"companions":[]}
{"id":688,"category":"Data Flow Safety","question":"How does EK9 enforce variable initialization order?","url":"https://ek9.io/qa/QA0688.html","alternatePhrasings":["What is E08010 variable used before defined?","What is E08020 variable used before initialized?","Does EK9 check that variables are initialized before use?","What happens if I reference an uninitialized variable?"],"answer":"EK9 tracks variable initialization state through the control flow and rejects any reference to a variable before it has been both declared and initialized.\n\nDECLARATION ORDER (E08010)\nVariables must be declared before any reference to them. A forward reference to a variable that has not yet been declared triggers E08010.\n\nINITIALIZATION TRACKING (E08020)\nEven after declaration, a variable must be initialized before use. If a variable is declared but not assigned a value before being read, E08020 is triggered.\n\nBRANCH ANALYSIS\nThe compiler tracks initialization across branches. If a variable is only initialized in one branch of an if/else, the compiler knows it may be uninitialized after the branch.\n\nCORRECT PATTERNS\n1. Declare and initialize together: name <- \"value\"\n2. Declare with type and initializer: name as String: \"value\"\n3. Guard-based initialization: if name <- getValue()\n\nSee Q632 for define-before-use. See Q633 for init across branches. See Q634 for guard before access.","ek9Example":"defines module qa.dataflow.initorder\n\n  defines function\n\n    <?-\n      Correct: each variable initialized before use.\n      Sequential computation with proper ordering.\n    -?>\n    calculateShipping() as pure\n      ->\n        itemWeight as Float\n        distanceKm as Float\n      <- shippingCost as Float: 0.0\n\n      baseRate <- 5.0\n      weightFactor <- itemWeight * 0.5\n      distanceFactor <- distanceKm * 0.1\n\n      shippingCost: baseRate + weightFactor + distanceFactor\n\n    <?-\n      Correct: guard-based initialization.\n      Variable is only used inside the guard where it is guaranteed set.\n    -?>\n    formatWeight() as pure\n      -> weightGrams as Float\n      <- formatted as String: \"unknown weight\"\n\n      if weightGrams > 0.0\n        kilograms <- weightGrams / 1000.0\n        formatted: `${kilograms} kg`\n\n    <?-\n      Correct: initialization across all branches.\n      Every branch assigns before later use.\n    -?>\n    classifyTemperature() as pure\n      -> celsius as Float\n      <- classification as String: \"moderate\"\n\n      freezingPoint <- 0.0\n      boilingPoint <- 100.0\n\n      if celsius < freezingPoint\n        classification: \"freezing\"\n      else if celsius > boilingPoint\n        classification: \"boiling\"\n\n  defines program\n\n    VariableInitOrderDemo()\n      stdout <- Stdout()\n\n      cost <- calculateShipping(2.5, 50.0)\n      stdout.println(`Shipping: ${cost}`)\n\n      stdout.println(formatWeight(1500.0))\n      stdout.println(formatWeight(0.0))\n\n      stdout.println(classifyTemperature(-5.0))\n      stdout.println(classifyTemperature(50.0))\n      stdout.println(classifyTemperature(150.0))","migrationContext":"Java: definite assignment analysis, compile error for uninitialized locals. Python: NameError at runtime. JavaScript: var hoisting, undefined. C: undefined behavior. Rust: ownership prevents use of moved values. EK9: compile-time initialization tracking with E08010 and E08020.","keywords":["E08010","E08020","data-flow","declaration","forward","initialization","initialize","order","reference","safety","variable"],"primaryTopics":[],"typicalErrors":[{"error":"E08010","correct":"baseRate <- 5.0\n      weightFactor <- itemWeight * 0.5\n      distanceFactor <- distanceKm * 0.1\n\n      shippingCost: baseRate + weightFactor + distanceFactor","incorrect":"shippingCost: baseRate + weightFactor + distanceFactor\n      baseRate <- 5.0","explanation":"A variable must be declared before any reference to it. Using baseRate before its declaration is a forward reference. See ek9 -h E08010 for details."},{"error":"E08050","correct":"<- classification as String: \"moderate\"","incorrect":"<- classification as String?","explanation":"A variable must be initialized before use. If classification has no default and is only assigned in one branch, it may be uninitialized when read. See ek9 -h E08050 for details."}],"companions":[]}
{"id":689,"category":"Comparison Patterns","question":"What are the correct patterns for boolean conditions and comparisons?","url":"https://ek9.io/qa/QA0689.html","alternatePhrasings":["What is E08082 type mismatch in comparison?","What is E08083 boolean literal in condition?","What is E08088 inconsistent ternary types?","How should boolean conditions be written in EK9?"],"answer":"EK9 enforces clean boolean condition patterns. Conditions must use variables (not literals), comparisons must have matching types, and ternary expressions must have consistent types.\n\nNO BOOLEAN LITERALS IN CONDITIONS (E08083)\nUsing 'true' or 'false' directly in a condition is flagged:\n  if true        // E08083: always true, dead code\n  if isValid     // Correct: use a boolean variable\n\nTYPE MATCHING IN COMPARISONS (E08082)\nBoth sides of a comparison must be compatible types:\n  if count > 0           // Correct: Integer > Integer\n  if name == 42          // E08082: String vs Integer\n\nCONSISTENT TERNARY TYPES (E08088)\nBoth branches of a ternary-style expression must produce the same type.\n\nVARIABLE-BASED CONDITIONS\nStore intermediate boolean results in named variables:\n  isAdult <- age >= adultThreshold\n  isVerified <- account.verified()\n  if isAdult and isVerified\n    grantAccess()\n\nSee Q637 for comparison pattern basics. See Q639 for boolean parameter patterns. See Q641 for capture requirements.","ek9Example":"defines module qa.comparison.booleanconditions\n\n  defines constant\n\n    ADULT_AGE_THRESHOLD <- 18\n    SENIOR_AGE_THRESHOLD <- 65\n    MINIMUM_BALANCE <- 100.0\n\n  defines function\n\n    <?-\n      Correct: boolean conditions use variables, not literals.\n      Named intermediates make intent clear.\n    -?>\n    classifyCustomer() as pure\n      ->\n        customerAge as Integer\n        accountBalance as Float\n      <- category as String: \"standard\"\n\n      isAdult <- customerAge >= ADULT_AGE_THRESHOLD\n      isSenior <- customerAge >= SENIOR_AGE_THRESHOLD\n      hasMinBalance <- accountBalance >= MINIMUM_BALANCE\n\n      if isSenior and hasMinBalance\n        category: \"premium senior\"\n      else if isAdult and hasMinBalance\n        category: \"premium\"\n      else if isAdult\n        category: \"standard\"\n      else\n        category: \"junior\"\n\n    <?-\n      Correct: comparisons use matching types.\n      Integer compared with Integer, String with String.\n    -?>\n    compareItems() as pure\n      ->\n        itemName as String\n        itemCount as Integer\n        targetName as String\n        targetCount as Integer\n      <- isMatch as Boolean: false\n\n      nameMatches <- itemName == targetName\n      countSufficient <- itemCount >= targetCount\n\n      isMatch: nameMatches and countSufficient\n\n  defines program\n\n    BooleanConditionPatternsDemo()\n      stdout <- Stdout()\n\n      stdout.println(classifyCustomer(70, 500.0))\n      stdout.println(classifyCustomer(25, 50.0))\n      stdout.println(classifyCustomer(15, 200.0))\n\n      matched <- compareItems(itemName: \"widget\", itemCount: 10, targetName: \"widget\", targetCount: 5)\n      stdout.println(`Match: ${matched}`)","migrationContext":"Java: boolean literals in conditions are valid (just warnings from linters). Python: no type checking for comparisons. C: any value in condition. Rust: condition must be bool. EK9: conditions must be boolean variables or expressions, no bare literals, types must match.","keywords":["E08082","E08083","E08088","boolean","comparison","condition","literal","pattern","ternary","type"],"primaryTopics":[],"typicalErrors":[{"error":"E07620","correct":"nameMatches <- itemName == targetName","incorrect":"nameMatches <- itemName == targetCount","explanation":"Both sides of a comparison must be compatible types. Comparing String with Integer is a type mismatch. See ek9 -h E07620 for details."},{"error":"E50060","correct":"classifyCustomer(70, 500.0)","incorrect":"classifyCustomer(70, 500.0).toUpperCase()","explanation":"String has no toUpperCase() method in EK9. Use upperCase() instead. See ek9 -h E50060 for details."},{"error":"E50060","correct":"stdout.println(`Match: ${matched}`)","incorrect":"stdout.println(matched.toString())","explanation":"Boolean has no toString() method in EK9; use the $ prefix operator (here via ${...} interpolation) to convert to String. See ek9 -h E50060 for details."}],"companions":[]}
{"id":690,"category":"Purity Contracts","question":"How do pure function call chains work and what restrictions apply?","url":"https://ek9.io/qa/QA0690.html","alternatePhrasings":["Why can't pure functions use mutation operators?","How does EK9 enforce transitive purity in call chains?","Can pure functions call other pure functions?","How does the transitive purity rule work?"],"answer":"Pure functions can only call other pure functions. This transitive rule ensures the entire call chain is free of side effects.\n\nTRANSITIVE PURITY (E08130)\nIf function A is pure and calls function B, then B must also be pure. If B calls C, then C must also be pure. The chain of purity is enforced transitively by the compiler.\n\nMUTATION PREVENTION (E08120)\nPure functions cannot mutate any state:\n  - Cannot reassign fields (use := on a property)\n  - Cannot use += on collections\n  - Cannot call methods that mutate state\n\nSEPARATE CHAINS\nA class can have both pure and non-pure method chains. The pure chain reads state, the non-pure chain modifies it. They must not cross.\n\nCORRECT PATTERN\n  transform() as pure calls validate() as pure calls normalize() as pure\n  update()            calls modify()            calls save()\n\nSee Q560 for pure method basics. See Q566 for pure method restrictions. See Q678 for purity override contract.","ek9Example":"defines module qa.purity.callchain\n\n  defines function\n\n    <?-\n      Pure function chain: each function calls only other pure functions.\n      Compiler verifies the entire chain is pure.\n    -?>\n    normalize() as pure\n      -> rawText as String\n      <- cleaned as String: rawText.trim()\n\n    validate() as pure\n      -> inputText as String\n      <- isValid as Boolean: false\n\n      normalized <- normalize(inputText)\n      isValid: normalized? and (length normalized > 0)\n\n    transform() as pure\n      -> sourceText as String\n      <- result as String: \"\"\n\n      if validate(sourceText)\n        result: normalize(sourceText).upperCase()\n\n  defines class\n\n    <?-\n      Class with separate pure and non-pure method chains.\n      Pure chain reads state, non-pure chain modifies state.\n    -?>\n    TextProcessor\n      entries <- List() of String\n\n      TextProcessor()\n        -> initialEntry as String\n        entries += initialEntry\n\n      //Pure chain: read-only\n      currentCount() as pure\n        <- rtn as Integer: length entries\n\n      findFirst() as pure\n        <- rtn as String: \"\"\n        firstEntry <- entries.getOrDefault(0, \"\")\n        if firstEntry?\n          rtn: transform(firstEntry)\n\n      //Non-pure chain: modifies state\n      addEntry()\n        -> newEntry as String\n        if validate(newEntry)\n          entries += normalize(newEntry)\n\n      default operator ?\n\n  defines program\n\n    PureCallChainDemo()\n      stdout <- Stdout()\n\n      stdout.println(transform(\"  hello world  \"))\n      stdout.println(`Valid: ${validate(\"test\")}`)\n      stdout.println(`Valid empty: ${validate(\"\")}`)\n\n      processor <- TextProcessor(\"first entry\")\n      processor.addEntry(\"  second  \")\n      stdout.println(`Count: ${processor.currentCount()}`)","migrationContext":"Java: no purity enforcement. Kotlin: no purity. Rust: shared references prevent mutation. Python: no purity. Haskell: IO monad separates pure from impure. EK9: 'as pure' keyword with transitive compile-time enforcement.","keywords":["E08120","E08130","call","chain","contract","function","immutable","mutation","pure","purity","restriction","side-effect","transitive"],"primaryTopics":[],"typicalErrors":[{"error":"E08120","correct":"currentCount() as pure\n        <- rtn as Integer: length entries","incorrect":"currentCount() as pure\n        <- rtn as Integer: 0\n        entries += \"extra\"\n        rtn: length entries","explanation":"Pure methods cannot mutate state. Using '+=' on a collection field inside a pure method triggers E08120. See ek9 -h E08120 for details."},{"error":"E08130","correct":"findFirst() as pure\n        <- rtn as String: \"\"\n        firstEntry <- entries.getOrDefault(0, \"\")\n        if firstEntry?\n          rtn: transform(firstEntry)","incorrect":"findFirst() as pure\n        <- rtn as String: \"\"\n        addEntry(\"injected\")\n        firstEntry <- entries.getOrDefault(0, \"\")\n        if firstEntry?\n          rtn: transform(firstEntry)","explanation":"A pure method cannot call a non-pure method. Since addEntry() modifies state, calling it from a pure method triggers E08130. The entire pure call chain must consist of pure functions only. See ek9 -h E08130 for details."}],"companions":[]}
{"id":691,"category":"DI Validation","question":"How does EK9 validate DI component registration and dependency ordering?","url":"https://ek9.io/qa/QA0691.html","alternatePhrasings":["What is E08200 DI registration order error?","Does EK9 validate dependency injection at compile time?","How must components be registered in an application?","What happens if a dependency is registered after its dependent?"],"answer":"EK9 validates the complete dependency injection graph at compile time. All components must be registered in the application, and the compiler checks that all dependencies can be satisfied.\n\nREGISTRATION REQUIREMENT (E08200)\nEvery abstract component that is injected must have a concrete implementation registered in the application. Missing registrations trigger E08200.\n\nDEPENDENCY CHAIN\nIf ComponentA depends on ComponentB, and ComponentB depends on ComponentC, all three must be registered. The compiler walks the entire dependency chain.\n\nABSTRACT INJECTION RULE\nDI only works with abstract components:\n  DataAccess as abstract       // Can be injected\n  DatabaseAccess extends DataAccess  // Concrete implementation\nConcrete components cannot be directly injected.\n\nAPPLICATION WIRING\nThe application block registers concrete implementations:\n  defines application\n    MyApp\n      register DatabaseAccess() as DataAccess\n      register OrderLogic() as BusinessLogic\n\nSee Q675 for transitive DI validation. See Q673 for missing registration. See Q674 for duplicate registration.","ek9Example":"defines module qa.divalidation.registrationorder\n\n  defines component\n\n    <?-\n      Layer 1: Abstract repository.\n      Bottom of the dependency chain.\n    -?>\n    Repository as abstract\n\n      findByKey() as abstract\n        -> lookupKey as String\n        <- record as String?\n\n      default operator ?\n\n    <?-\n      Concrete repository implementation.\n    -?>\n    InMemoryRepository extends Repository\n\n      override findByKey()\n        -> lookupKey as String\n        <- record as String: \"\"\n\n        record: \"record:\" + lookupKey\n\n      default operator ?\n\n    <?-\n      Layer 2: Abstract service.\n      Depends on Repository (injected).\n    -?>\n    OrderService as abstract\n\n      processOrder() as abstract\n        -> orderId as String\n        <- confirmation as String?\n\n      default operator ?\n\n    <?-\n      Concrete service implementation.\n      Injects Repository (must be registered).\n    -?>\n    DefaultOrderService extends OrderService\n\n      repository as Repository!\n\n      override processOrder()\n        -> orderId as String\n        <- confirmation as String: \"\"\n\n        if lookupResult <- repository.findByKey(orderId)\n          confirmation: \"Confirmed: \" + lookupResult\n\n      default operator ?\n\n    <?-\n      Layer 3: Abstract coordinator.\n      Depends on OrderService (injected).\n    -?>\n    Coordinator as abstract\n\n      coordinate() as abstract\n        -> taskId as String\n        <- outcome as String?\n\n      default operator ?\n\n    <?-\n      Concrete coordinator implementation.\n      Injects OrderService (transitive chain).\n    -?>\n    OrderCoordinator extends Coordinator\n\n      orderService as OrderService!\n\n      override coordinate()\n        -> taskId as String\n        <- outcome as String: \"\"\n\n        if result <- orderService.processOrder(taskId)\n          outcome: \"Coordinated: \" + result\n\n      default operator ?\n\n  defines application\n\n    <?-\n      Application must register ALL levels of the dependency chain.\n      Missing any level triggers E08200.\n    -?>\n    OrderApp\n      register InMemoryRepository() as Repository\n      register DefaultOrderService() as OrderService\n      register OrderCoordinator() as Coordinator\n\n  defines program\n\n    DiRegistrationOrderDemo() with application of OrderApp\n      stdout <- Stdout()\n\n      coordinator as Coordinator!\n      if outcome <- coordinator.coordinate(\"ORD-001\")\n        stdout.println(outcome)\n\n      stdout.println(\"All three layers registered and validated at compile time\")","migrationContext":"Java: Spring validates at startup (runtime). Python: runtime discovery. Go: manual wiring (compile-time). Rust: no DI framework. Kotlin: Koin validates at runtime. EK9: full compile-time DI validation of the complete dependency graph.","keywords":["DI","E08200","application","compile-time","component","dependency","inject","injection","order","registration","sanitize","validate"],"primaryTopics":[],"typicalErrors":[{"error":"E50010","correct":"OrderApp\n      register InMemoryRepository() as Repository\n      register DefaultOrderService() as OrderService\n      register OrderCoordinator() as Coordinator","incorrect":"IncompleteApp\n      register OrderCoordinator() as Coordinator","explanation":"The compiler validates that all components in the dependency chain have registrations. OrderCoordinator depends on OrderService, which depends on Repository. All three must be registered in the application. Missing any registration in the chain triggers this error. See ek9 -h E50010 for details."}],"companions":[]}
{"id":692,"category":"Data Flow Safety","question":"How does EK9 ensure class fields are properly initialized?","url":"https://ek9.io/qa/QA0692.html","alternatePhrasings":["What is E08180 field not initialized?","Must all class fields be initialized in EK9?","What happens if a field is not initialized in the constructor?","How do I initialize fields in EK9 classes?"],"answer":"EK9 requires all class fields to be initialized either inline at declaration or through constructors. Uninitialized fields trigger E08180.\n\nINLINE INITIALIZATION\nThe simplest approach initializes fields at declaration:\n  name <- String()             // Initialized to empty string\n  count as Integer: Integer()  // Explicit type with initializer\n\nCONSTRUCTOR INITIALIZATION\nConstructors can initialize fields using ':' assignment:\n  MyClass()\n    -> name as String\n    this.name: name\n\nDEFAULT CONSTRUCTOR PATTERN\nUse 'default ClassName()' to generate a no-arg constructor that initializes all fields to their defaults.\n\nUNSET FIELDS\nFields declared with '?' are initially unset but exist:\n  name as String?\nThey must still be declared with a type.\n\nALL FIELDS MUST EXIST\nEvery field must be declared in the class body. The compiler verifies that every field has a type and can be initialized.\n\nSee Q632 for define-before-use. See Q688 for variable init order. See Q563 for pure constructor assignment.","ek9Example":"defines module qa.dataflow.fieldinit\n\n  defines class\n\n    <?-\n      Inline initialization: fields get default values at declaration.\n      This is the simplest and most common pattern.\n    -?>\n    Configuration\n      hostName <- \"localhost\"\n      portNumber <- 8080\n      isEnabled <- true\n\n      getHostName() as pure\n        <- rtn as String: hostName\n\n      getPortNumber() as pure\n        <- rtn as Integer: portNumber\n\n      getIsEnabled() as pure\n        <- rtn as Boolean: isEnabled\n\n      default operator ?\n\n    <?-\n      Constructor initialization: fields set from parameters.\n      Each field assigned in the constructor body.\n    -?>\n    UserProfile\n      userName <- String()\n      emailAddress <- String()\n      loginCount <- Integer()\n\n      UserProfile()\n        ->\n          userName as String\n          emailAddress as String\n        this.userName: userName\n        this.emailAddress: emailAddress\n\n      getUserName() as pure\n        <- rtn as String: userName\n\n      getEmailAddress() as pure\n        <- rtn as String: emailAddress\n\n      incrementLogin()\n        loginCount: loginCount + 1\n\n      default operator ?\n\n    <?-\n      Unset field pattern. A '?' field (query) must still be initialised by\n      every public construction route: the constructor below sets it.\n      A field assigned only later (resultCount, via setResultCount) is given a\n      declaration initialiser to a present-but-unset value, so every route\n      leaves it initialised; it reads as unset until setResultCount is called.\n    -?>\n    SearchResult\n      query as String?\n      resultCount as Integer: Integer()\n\n      default private SearchResult()\n\n      SearchResult()\n        -> query as String\n        this.query: query\n\n      setResultCount()\n        -> resultCount as Integer\n        this.resultCount: resultCount\n\n      describe()\n        <- rtn as String: \"\"\n        if query?\n          if resultCount?\n            rtn: `${query}: ${resultCount} results`\n          else\n            rtn: `${query}: pending`\n\n      default operator ?\n\n  defines program\n\n    FieldInitializationDemo()\n      stdout <- Stdout()\n\n      config <- Configuration()\n      stdout.println(`Host: ${config.getHostName()}:${config.getPortNumber()}`)\n\n      profile <- UserProfile(\"alice\", \"alice@example.com\")\n      stdout.println(`User: ${profile.getUserName()}`)\n\n      search <- SearchResult(\"ek9 tutorials\")\n      stdout.println(search.describe())\n      search.setResultCount(42)\n      stdout.println(search.describe())","migrationContext":"Java: fields have default values (null, 0, false). Python: fields set in __init__, no compile-time check. C++: fields uninitialized unless in initializer list. Rust: all fields must be initialized. Go: fields zero-initialized. EK9: all fields must be explicitly initialized or declared with default.","keywords":["E08180","class","constructor","data-flow","declaration","default","field","initialization","initialize","inline","safety"],"primaryTopics":[],"typicalErrors":[{"error":"E08180","correct":"hostName <- \"localhost\"\n      portNumber <- 8080\n      isEnabled <- true","incorrect":"hostName as String\n      portNumber as Integer\n      isEnabled as Boolean","explanation":"All class fields must be initialized either inline at declaration or through constructors. Declaring fields without initialization or a default leaves them in an undefined state. See ek9 -h E08180 for details."}],"companions":[]}
{"id":693,"category":"Code Quality","question":"Why must operator and pure function return values be captured?","url":"https://ek9.io/qa/QA0693.html","alternatePhrasings":["What is E11050 uncaptured operator return?","What is E11051 uncaptured pure function return?","What is E11052 uncaptured Result return?","Why does EK9 require capturing return values?"],"answer":"EK9 requires that return values from constructors, operators, pure functions, and Result-producing functions are captured in variables. Discarding these values indicates a bug.\n\nCONSTRUCTOR RETURNS (E11055)\nA constructor creates an object. Calling a constructor as a bare statement discards the result immediately. This is always either a bug (forgot the assignment) or a misuse of constructors for side effects:\n  Sensor(98.6)          // E11055: constructor result discarded\n  sensor <- Sensor(98.6)  // Correct: captured\n\nOPERATOR RETURNS (E11050)\nOperators like + and * always produce new values. Discarding an operator result means the computation was pointless:\n  name + \" suffix\"     // E11050: result discarded\n  combined <- name + \" suffix\"  // Correct: captured\n\nPURE FUNCTION RETURNS (E11051)\nPure functions have no side effects. If the return value is discarded, the call accomplished nothing:\n  validate(input)       // E11051: pure result discarded\n  isValid <- validate(input)  // Correct: captured\n\nRESULT RETURNS (E11052)\nResult-producing functions may fail. Discarding the Result ignores potential errors:\n  parseAge(text)        // E11052: Result discarded\n  result <- parseAge(text)  // Correct: captured\n\nSee Q310 for code quality overview. See Q316 for discarded returns detail. See Q321 for naming conventions.\nSee Q732 for discarded return boundary examples.","ek9Example":"defines module qa.codequality.capturedreturns\n\n  defines function\n\n    <?-\n      Pure function: return value must be captured.\n      Discarding a pure return is always a bug.\n    -?>\n    sanitizeInput() as pure\n      -> rawInput as String\n      <- cleaned as String: rawInput.trim()\n\n    <?-\n      Another pure function in the chain.\n    -?>\n    validateLength() as pure\n      -> inputText as String\n      <- isValid as Boolean: length inputText > 0\n\n  defines class\n\n    <?-\n      Class demonstrating correct return value capture.\n      Every operator and pure return is stored in a variable.\n    -?>\n    TextBuffer\n      content <- String()\n\n      TextBuffer()\n        -> initialContent as String\n        this.content: initialContent\n\n      getContent() as pure\n        <- rtn as String: content\n\n      append()\n        -> suffix as String\n        content: content + suffix\n\n      default operator ?\n\n  defines program\n\n    CapturedOperatorReturnsDemo()\n      stdout <- Stdout()\n\n      //Correct: pure function return captured\n      cleaned <- sanitizeInput(\"  hello  \")\n      stdout.println(cleaned)\n\n      //Correct: pure function return captured\n      isValid <- validateLength(cleaned)\n      stdout.println(`Valid: ${isValid}`)\n\n      //Correct: operator return captured\n      combined <- cleaned + \" world\"\n      stdout.println(combined)\n\n      //Correct: method return captured\n      textStore <- TextBuffer(\"start\")\n      storeContent <- textStore.getContent()\n      stdout.println(storeContent)","migrationContext":"Java: return values can be silently discarded. Rust: #[must_use] annotation (optional). Go: blank identifier _ explicitly discards. Python: no enforcement. C++: [[nodiscard]] attribute (optional). EK9: all operator, pure, and Result returns must be captured (compile error, not warning).","keywords":["E11050","E11051","E11052","E11055","Result","capture","clean-code","constructor","discard","function","immutable","metric","operator","pure","quality","return","side-effect"],"primaryTopics":[],"typicalErrors":[{"error":"E50060","correct":"stdout.println(cleaned)","incorrect":"stdout.println(cleaned.toUpperCase())","explanation":"String has no toUpperCase() method in EK9. Use upperCase() instead. See ek9 -h E50060 for details."},{"error":"E50060","correct":"combined <- cleaned + \" world\"","incorrect":"combined <- cleaned.toUpperCase() + \" world\"","explanation":"String has no toUpperCase() method in EK9. Use upperCase() instead. See ek9 -h E50060 for details."},{"error":"E50001","correct":"cleaned <- sanitizeInput(\"  hello  \")","incorrect":"sanitizeInput(\"  hello  \")","explanation":"Removing the variable declaration means later references to the variable become unresolved, triggering E50001. See ek9 -h E50001 for details."},{"error":"E50001","correct":"storeContent <- textStore.getContent()","incorrect":"textStore.getContent()","explanation":"The pure method getContent returns a value that must be captured. Discarding a pure return is always a bug. See ek9 -h E50001 for details."}],"companions":[]}
{"id":694,"category":"Code Quality","question":"When must named arguments be used in function calls?","url":"https://ek9.io/qa/QA0694.html","alternatePhrasings":["What is E11061 boolean positional argument?","What is E11062 too many positional arguments?","When are named arguments required in EK9?","How do I use named arguments for clarity?"],"answer":"EK9 requires named arguments in two situations: when passing Boolean values as positional arguments, and when calling functions with 4 or more parameters.\n\nBOOLEAN POSITIONAL ARGS (E11061)\nBoolean arguments are ambiguous when positional:\n  setMode(true, false)          // E11061: which is which?\n  setMode(verbose: true, debug: false)  // Correct: named\n\n4+ POSITIONAL ARGS (E11062)\nFunctions with 4 or more parameters require named arguments:\n  configure(\"host\", 8080, true, \"admin\")  // E11062\n  configure(host: \"host\", port: 8080, secure: true, user: \"admin\")  // Correct\n\nWHY THIS MATTERS\nNamed arguments make call sites self-documenting. Without names, readers must look up the function signature to understand what each argument means.\n\nFEWER ARGS OK\nFunctions with 1-3 non-Boolean parameters can use positional arguments:\n  greet(\"Alice\", \"morning\")  // OK: only 2 args, no Booleans\n\nSee Q314 for naming conventions. See Q322 for code quality metrics. See Q689 for boolean condition patterns.","ek9Example":"defines module qa.codequality.namedarguments\n\n  defines function\n\n    <?-\n      Function with 4+ parameters.\n      Callers MUST use named arguments.\n    -?>\n    createConnection()\n      ->\n        hostName as String\n        portNumber as Integer\n        useTls as Boolean\n        connectionTimeout as Integer\n      <- connectionInfo as String: `${hostName}:${portNumber} timeout=${connectionTimeout}`\n\n      if useTls\n        connectionInfo: connectionInfo + \" TLS\"\n\n    <?-\n      Function with fewer parameters: positional OK.\n    -?>\n    formatGreeting() as pure\n      ->\n        recipientName as String\n        greeting as String\n      <- message as String: `${greeting}, ${recipientName}!`\n\n    <?-\n      Function with Boolean parameter: must use named arg.\n    -?>\n    formatOutput() as pure\n      ->\n        content as String\n        includeTimestamp as Boolean\n      <- formatted as String: content\n\n      if includeTimestamp\n        formatted: \"[timestamp] \" + content\n\n  defines program\n\n    NamedArgumentsPatternDemo()\n      stdout <- Stdout()\n\n      //4+ params: named arguments required\n      connInfo <- createConnection(hostName: \"db.example.com\", portNumber: 5432, useTls: true, connectionTimeout: 30)\n      stdout.println(connInfo)\n\n      //2 non-Boolean params: positional OK\n      greeting <- formatGreeting(\"Alice\", \"Good morning\")\n      stdout.println(greeting)\n\n      //Boolean param: named argument required\n      output <- formatOutput(content: \"hello\", includeTimestamp: true)\n      stdout.println(output)","migrationContext":"Java: no named argument support. Python: named arguments optional. Kotlin: named arguments optional but encouraged. Swift: named arguments required by default. Go: no named arguments. EK9: named arguments mandatory for 4+ params and Boolean params.","keywords":["Boolean","E11061","E11062","arguments","clarity","clean-code","function","metric","named","parameter","positional","quality"],"primaryTopics":[],"typicalErrors":[{"error":"E50060","correct":"stdout.println(output)","incorrect":"stdout.println(output.toUpperCase())","explanation":"String has no toUpperCase() method in EK9. Use upperCase() instead. See ek9 -h E50060 for details."},{"error":"E50060","correct":"stdout.println(connInfo)","incorrect":"stdout.println(connInfo.toString())","explanation":"String has no toString() method in EK9. The variable is already a String. See ek9 -h E50060 for details."}],"companions":[]}
{"id":695,"category":"Code Quality","question":"Why must comparison values use named constants instead of bare literals?","url":"https://ek9.io/qa/QA0695.html","alternatePhrasings":["What is E11064 magic literal in comparison?","How do I avoid magic numbers in EK9?","Must I extract all literals to constants?","What triggers the magic literal check?"],"answer":"EK9 detects 'magic literals' in comparisons and requires them to be extracted to named constants. This makes code self-documenting and maintainable.\n\nMAGIC LITERAL DETECTION (E11064)\nBare numeric or string literals in comparisons are flagged:\n  if temperature > 100.0        // E11064: what does 100.0 mean?\n  if temperature > BOILING_POINT // Correct: named constant\n\nCONSTANT DECLARATION\nUse 'defines constant' to declare named values:\n  defines constant\n    BOILING_POINT <- 100.0\n    FREEZING_POINT <- 0.0\n    MAX_RETRIES <- 3\n\nWHAT TRIGGERS E11064\nLiterals in comparison expressions (==, <>, <, >, <=, >=) are checked. Literals in declarations and assignments are not flagged.\n\nBENEFITS\n1. Self-documenting: BOILING_POINT vs 100.0\n2. Single source of truth: change one constant, all comparisons update\n3. Searchable: find all uses of BOILING_POINT\n\nSee Q310 for code quality overview. See Q313 for coding standards. See Q694 for named arguments.","ek9Example":"defines module qa.codequality.namedconstants\n\n  defines constant\n\n    //Named constants make comparisons self-documenting\n    FREEZING_CELSIUS <- 0.0\n    BOILING_CELSIUS <- 100.0\n    ADULT_THRESHOLD <- 18\n    RETIREMENT_THRESHOLD <- 65\n    MAX_PASSWORD_LENGTH <- 128\n    MIN_PASSWORD_LENGTH <- 8\n\n  defines function\n\n    <?-\n      Correct: comparisons use named constants.\n      Every threshold value has a meaningful name.\n    -?>\n    classifyWater() as pure\n      -> temperatureCelsius as Float\n      <- phase as String: \"liquid\"\n\n      if temperatureCelsius <= FREEZING_CELSIUS\n        phase: \"solid\"\n      else if temperatureCelsius >= BOILING_CELSIUS\n        phase: \"gas\"\n\n    <?-\n      Correct: age thresholds as named constants.\n    -?>\n    ageCategory() as pure\n      -> personAge as Integer\n      <- category as String: \"adult\"\n\n      if personAge < ADULT_THRESHOLD\n        category: \"minor\"\n      else if personAge >= RETIREMENT_THRESHOLD\n        category: \"senior\"\n\n    <?-\n      Correct: password length limits as named constants.\n    -?>\n    validatePasswordLength() as pure\n      -> passwordText as String\n      <- isValid as Boolean: false\n\n      passwordLength <- length passwordText\n      isValid: passwordLength >= MIN_PASSWORD_LENGTH and passwordLength <= MAX_PASSWORD_LENGTH\n\n  defines program\n\n    NamedConstantsDemo()\n      stdout <- Stdout()\n\n      stdout.println(classifyWater(-5.0))\n      stdout.println(classifyWater(50.0))\n      stdout.println(classifyWater(105.0))\n\n      stdout.println(ageCategory(15))\n      stdout.println(ageCategory(30))\n      stdout.println(ageCategory(70))\n\n      stdout.println(`Valid password: ${validatePasswordLength(\"secureP1\")}`)\n      stdout.println(`Too short: ${validatePasswordLength(\"abc\")}`)","migrationContext":"Java: no magic literal enforcement (only static analysis tools). Python: no enforcement. C++: no enforcement. Rust: Clippy warns about magic numbers. Go: no enforcement. EK9: compile error for magic literals in comparisons.","keywords":["E11064","clean-code","comparison","constant","literal","magic","metric","migrate","named","quality","self-documenting"],"primaryTopics":[],"typicalErrors":[{"error":"E50060","correct":"stdout.println(classifyWater(-5.0))","incorrect":"stdout.println(classifyWater(-5.0).toUpperCase())","explanation":"String has no toUpperCase() method in EK9. Use upperCase() instead. See ek9 -h E50060 for details."}],"companions":[]}
{"id":696,"category":"Code Quality","question":"How does EK9 enforce complexity limits and what triggers E11010?","url":"https://ek9.io/qa/QA0696.html","alternatePhrasings":["What is E11010 cyclomatic complexity exceeded?","What is E11012 method too complex?","How do I keep functions within complexity limits?","What are the complexity thresholds in EK9?"],"answer":"EK9 measures cyclomatic complexity per function and method. Exceeding the threshold triggers a compile error, forcing decomposition into smaller units.\n\nCYCLOMATIC COMPLEXITY (E11010)\nEach branch point (if, else, switch case, for, while, guard) adds one to complexity. The compiler sets a threshold and rejects functions that exceed it.\n\nMETHOD COMPLEXITY (E11012)\nMethods within classes are checked individually. A method with too many branch points triggers E11012.\n\nSTAYING WITHIN LIMITS\n1. Decompose into smaller pure functions\n2. Use guard expressions to flatten nesting\n3. Use switch instead of chained if/else\n4. Extract complex conditions into named Boolean variables\n\nFUNCTIONAL DECOMPOSITION\nEK9 encourages extracting logic into small, focused pure functions:\n  // Instead of one complex function:\n  //   processOrder() with 20 branches\n  // Use:\n  //   validateOrder() + calculateTotal() + applyDiscount()\n\nSee Q316 for complexity metrics. See Q322 for quality enforcement. See Q310 for code quality overview.\nSee Q728 for nesting depth boundary. See Q733 for combined complexity boundary.","ek9Example":"defines module qa.codequality.complexitylimits\n\n  defines constant\n\n    LOW_THRESHOLD <- 10\n    MEDIUM_THRESHOLD <- 50\n    HIGH_THRESHOLD <- 90\n    PASSING_SCORE <- 60\n\n  defines function\n\n    <?-\n      Small focused function: low complexity.\n      Single responsibility, easy to test.\n    -?>\n    classifyScore() as pure\n      -> scoreValue as Integer\n      <- classification as String: \"average\"\n\n      if scoreValue < LOW_THRESHOLD\n        classification: \"very low\"\n      else if scoreValue < MEDIUM_THRESHOLD\n        classification: \"low\"\n      else if scoreValue < HIGH_THRESHOLD\n        classification: \"high\"\n      else\n        classification: \"very high\"\n\n    <?-\n      Another small function: validates a single aspect.\n    -?>\n    isPassing() as pure\n      -> scoreValue as Integer\n      <- passing as Boolean: scoreValue >= PASSING_SCORE\n\n    <?-\n      Composing small functions keeps complexity low.\n      Each function does one thing, combining them is straightforward.\n    -?>\n    generateReport() as pure\n      -> scoreValue as Integer\n      <- report as String: \"\"\n\n      classification <- classifyScore(scoreValue)\n      passed <- isPassing(scoreValue)\n\n      if passed\n        report: `${classification} (PASS)`\n      else\n        report: `${classification} (FAIL)`\n\n  defines program\n\n    ComplexityWithinLimitsDemo()\n      stdout <- Stdout()\n\n      scores <- [85, 42, 95, 15, 60]\n      for scoreValue in scores\n        stdout.println(generateReport(scoreValue))","migrationContext":"Java: complexity checked by static analysis tools (optional). Python: no built-in check. C++: no built-in check. Rust: Clippy complexity warning (optional). Go: no built-in check. EK9: complexity is a compile error, not a warning.","keywords":["E11010","E11012","clean-code","complexity","cyclomatic","decomposition","limit","metric","quality","threshold"],"primaryTopics":[],"typicalErrors":[{"error":"E50060","correct":"stdout.println(generateReport(scoreValue))","incorrect":"stdout.println(generateReport(scoreValue).toUpperCase())","explanation":"String has no toUpperCase() method in EK9. Use upperCase() instead. See ek9 -h E50060 for details."},{"error":"E50060","correct":"classification <- classifyScore(scoreValue)","incorrect":"classification <- classifyScore(scoreValue).toUpperCase()","explanation":"String has no toUpperCase() method in EK9. Use upperCase() instead. See ek9 -h E50060 for details."}],"companions":[]}
{"id":697,"category":"Type Hierarchy Constraints","question":"How does EK9 limit inheritance depth and what happens when exceeded?","url":"https://ek9.io/qa/QA0697.html","alternatePhrasings":["What is E11013 inheritance depth exceeded?","How deep can class hierarchies be in EK9?","Is there a maximum inheritance depth?","Why does EK9 limit inheritance chain length?"],"answer":"EK9 limits the depth of class inheritance hierarchies. Excessively deep hierarchies are a code smell that indicates design problems.\n\nDEPTH LIMIT (E11013)\nThe compiler tracks the number of levels in an inheritance chain. When the chain exceeds the threshold, E11013 is raised. This forces redesign toward flatter hierarchies.\n\nWHY LIMIT DEPTH\nDeep hierarchies create problems:\n1. Fragile base class: changes at the top cascade unpredictably\n2. Understanding difficulty: must read 6+ classes to understand behavior\n3. Constructor complexity: super() chains become error-prone\n4. Testing complexity: each level adds testing permutations\n\nCORRECT APPROACH\nPrefer composition and trait-based design over deep inheritance:\n  // Instead of: A -> B -> C -> D -> E -> F\n  // Use: F with trait of Auditable, Printable, Validatable\n\nACCEPTABLE DEPTH\nA 3-5 level hierarchy is typically acceptable:\n  Base -> Intermediate -> Concrete\n  Shape -> Polygon -> Triangle\n\nSee Q611 for hierarchy validation summary. See Q677 for abstract implementation chain. See Q602 for circular hierarchy detection.\nSee Q729 for inheritance depth boundary example.","ek9Example":"defines module qa.typehierarchy.depthlimit\n\n  defines trait\n\n    //Traits provide behavior without deep hierarchies\n    Auditable\n      auditLog() as pure\n        <- rtn as String: \"audited\"\n\n    Printable\n      printLabel() as pure\n        <- rtn as String: \"printable\"\n\n  defines class\n\n    //Level 1: abstract base\n    Entity as abstract\n      entityType() as pure abstract\n        <- rtn as String?\n      default operator ?\n\n    //Level 2: intermediate with common behavior\n    NamedEntity extends Entity as open\n      entityName <- String()\n\n      NamedEntity()\n        -> entityName as String\n        this.entityName: entityName\n\n      getName() as pure\n        <- rtn as String: entityName\n\n      override entityType() as pure\n        <- rtn as String: \"named\"\n\n      default operator ?\n\n    //Level 3: concrete with traits for additional behavior\n    Customer extends NamedEntity with trait of Auditable, Printable\n      customerId <- String()\n\n      Customer()\n        ->\n          customerName as String\n          customerId as String\n        super(customerName)\n        this.customerId: customerId\n\n      getCustomerId() as pure\n        <- rtn as String: customerId\n\n      override entityType() as pure\n        <- rtn as String: \"customer\"\n\n      override auditLog() as pure\n        <- rtn as String: `customer:${customerId}`\n\n      override printLabel() as pure\n        <- rtn as String: `${getName()} (${customerId})`\n\n      default operator ?\n\n  defines program\n\n    InheritanceDepthLimitDemo()\n      stdout <- Stdout()\n\n      customer <- Customer(customerName: \"Alice\", customerId: \"C001\")\n      stdout.println(customer.printLabel())\n      stdout.println(customer.auditLog())\n      stdout.println(customer.entityType())","migrationContext":"Java: no depth limit (static analysis tools may warn). Python: MRO handles deep hierarchies but they are a smell. C++: no depth limit. Kotlin: no depth limit. Rust: no inheritance, uses trait composition. Go: no inheritance, uses embedding. EK9: compile-time depth limit to enforce good design.","keywords":["E11013","chain","circular","composition","depth","design","hierarchy","inheritance","limit","type"],"primaryTopics":[],"typicalErrors":[{"error":"E50060","correct":"stdout.println(customer.printLabel())","incorrect":"stdout.println(customer.printLabel().toUpperCase())","explanation":"String has no toUpperCase() method in EK9. Use upperCase() instead. See ek9 -h E50060 for details."}],"companions":[]}
{"id":698,"category":"Code Quality","question":"How does EK9 prevent excessive Boolean parameters?","url":"https://ek9.io/qa/QA0698.html","alternatePhrasings":["What is E11061 excessive Boolean parameter?","Why does EK9 flag Boolean parameters?","How should I handle methods with Boolean flags?","What is the correct pattern for Boolean parameters?"],"answer":"EK9 flags Boolean parameters that create unclear APIs. Boolean arguments at call sites are ambiguous without named arguments.\n\nBOOLEAN PARAMETER WARNING (E11061)\nMethods with Boolean parameters require callers to use named arguments to avoid ambiguity:\n  process(true, false)           // E11061: ambiguous\n  process(verbose: true, debug: false)  // Clear\n\nWHY BOOLEANS ARE PROBLEMATIC\nAt the call site, bare true/false conveys no meaning:\n  createUser(\"Alice\", true, false, true)  // What do these mean?\n\nCORRECT PATTERNS\n1. Use named arguments for Boolean parameters\n2. Store Boolean values in named variables before passing\n3. Consider using enumerations instead of Boolean flags\n\nENUMERATION ALTERNATIVE\nInstead of Boolean flags:\n  LogLevel with INFO, DEBUG, TRACE\n  setLogging(level: LogLevel.DEBUG)\n\nNAMED VARIABLE PATTERN\n  isVerbose <- true\n  setMode(verbose: isVerbose)\n\nSee Q694 for named arguments pattern. See Q689 for boolean condition patterns. See Q314 for naming conventions.","ek9Example":"defines module qa.codequality.booleanparams\n\n  defines function\n\n    <?-\n      Function with Boolean parameter.\n      Callers must use named argument for the Boolean.\n    -?>\n    formatMessage()\n      ->\n        messageText as String\n        includePrefix as Boolean\n        useColor as Boolean\n      <- formatted as String: messageText\n\n      if includePrefix\n        formatted: \"[MSG] \" + messageText\n\n      if useColor\n        formatted: `<c>${formatted}</c>`\n\n    <?-\n      Using named variables for Boolean values.\n      The variable name documents the intent.\n    -?>\n    processEntry()\n      ->\n        entryText as String\n        shouldValidate as Boolean\n      <- result as String: entryText\n\n      if shouldValidate\n        trimmed <- entryText.trim()\n        if trimmed?\n          result: trimmed\n\n  defines program\n\n    ExcessiveBooleanParamsDemo()\n      stdout <- Stdout()\n\n      //Named arguments for Booleans: clear intent\n      msg <- formatMessage(messageText: \"hello\", includePrefix: true, useColor: false)\n      stdout.println(msg)\n\n      //Named variable pattern: descriptive name\n      needsValidation <- true\n      processed <- processEntry(entryText: \"  data  \", shouldValidate: needsValidation)\n      stdout.println(processed)","migrationContext":"Java: no enforcement (spotbugs may warn). Python: no enforcement. Kotlin: named arguments help but not required. Swift: external parameter names help. Go: no enforcement. EK9: Boolean params require named arguments at call site.","keywords":["Boolean","E11061","ambiguous","argument","clarity","clean-code","flag","metric","named","parameter","quality"],"primaryTopics":[],"typicalErrors":[{"error":"E11061","correct":"formatMessage(messageText: \"hello\", includePrefix: true, useColor: false)","incorrect":"formatMessage(\"hello\", true, false)","explanation":"Boolean arguments must be passed BY NAME so each true/false is self-documenting. Passing two positional Boolean literals (true, false) triggers E11061 - name them (includePrefix: true, useColor: false). See ek9 -h E11061 for details."}],"companions":[]}
{"id":699,"category":"Override Mechanics","question":"What are the return type and parameter type rules for method overrides?","url":"https://ek9.io/qa/QA0699.html","alternatePhrasings":["What is E05160 cannot add purity in override?","What is E05170 parameter type mismatch in override?","What is E05180 return type mismatch in override?","Must override methods have identical signatures?"],"answer":"EK9 requires that override methods match the parent method signature exactly. Return types and parameter types must be identical.\n\nRETURN TYPE MATCHING (E05180)\nThe return type of an override must match the parent exactly:\n  Parent: process() <- rtn as String\n  Child:  override process() <- rtn as String    // Correct\n  Child:  override process() <- rtn as Integer   // E05180\n\nPARAMETER TYPE MATCHING (E05170)\nParameter types must also match exactly:\n  Parent: handle(input as String)\n  Child:  override handle(input as String)    // Correct\n  Child:  override handle(input as Integer)   // E05170\n\nPURITY CANNOT BE ADDED (E05160)\nIf the parent method is NOT pure, the override cannot add purity:\n  Parent: compute()          // Not pure\n  Child:  override compute() as pure  // E05160\nPurity must be designed in at the base level.\n\nWHY EXACT MATCHING\nExact matching ensures Liskov Substitution. Any code using the parent type will work correctly with any descendant.\n\nSee Q570 for override basics. See Q573 for access modifier rules. See Q567 for purity chain inheritance.","ek9Example":"defines module qa.override.returntypecovariance\n\n  defines class\n\n    <?-\n      Base class defining the method contract.\n      Return types and parameter types set here.\n    -?>\n    Formatter as open\n\n      //Method with String return and String parameter\n      formatItem()\n        -> itemText as String\n        <- formatted as String: `[${itemText}]`\n\n      //Pure method: descendants must keep purity\n      summarize() as pure\n        <- rtn as String: \"base summary\"\n\n      //Method with multiple parameters\n      combine()\n        ->\n          firstPart as String\n          secondPart as String\n        <- combined as String: `${firstPart} ${secondPart}`\n\n      default operator ?\n\n    <?-\n      Correct override: all types match exactly.\n      Return types, parameter types, and purity all preserved.\n    -?>\n    DetailFormatter extends Formatter\n\n      //Correct: String -> String, same types\n      override formatItem()\n        -> itemText as String\n        <- formatted as String: `<< ${itemText} >>`\n\n      //Correct: pure override of pure method\n      override summarize() as pure\n        <- rtn as String: \"detail summary\"\n\n      //Correct: (String, String) -> String, same types\n      override combine()\n        ->\n          firstPart as String\n          secondPart as String\n        <- combined as String: `${firstPart} | ${secondPart}`\n\n      default operator ?\n\n  defines program\n\n    ReturnTypeCovarianceDemo()\n      stdout <- Stdout()\n\n      base <- Formatter()\n      stdout.println(base.formatItem(\"item\"))\n      stdout.println(base.summarize())\n\n      detail <- DetailFormatter()\n      stdout.println(detail.formatItem(\"item\"))\n      stdout.println(detail.summarize())\n      stdout.println(detail.combine(firstPart: \"left\", secondPart: \"right\"))","migrationContext":"Java: covariant return types allowed, parameter types must match. Kotlin: covariant returns, exact params. C++: covariant returns. Rust: no inheritance, trait impls must match exactly. EK9: all types must match exactly (no covariance).","keywords":["E05160","E05170","E05180","abstract","covariance","function","inherit","open","override","parameter","return","signature","type","virtual"],"primaryTopics":[],"typicalErrors":[{"error":"E05150","correct":"override summarize() as pure","incorrect":"override summarize()","explanation":"If the parent method is pure, the override must remain pure. Removing purity from an override is not allowed. Conversely, adding purity to an override of a non-pure method also triggers E05150. See ek9 -h E05150 for details."},{"error":"E05110","correct":"override formatItem()\n        -> itemText as String","incorrect":"override formatItem()\n        -> itemText as Integer","explanation":"Parameter types in an override must match the parent exactly. Changing String to Integer means the method no longer overrides the parent and triggers E05110. See ek9 -h E05110 for details."},{"error":"E07440","correct":"override formatItem()\n        -> itemText as String\n        <- formatted as String: `<< ${itemText} >>`","incorrect":"override formatItem()\n        -> itemText as String\n        <- formatted as Integer: 42","explanation":"The return type of an override must match the parent exactly. EK9 does not support covariant return types, so changing String to Integer triggers E07440. See ek9 -h E07440 for details."}],"companions":[]}
{"id":700,"category":"Code Quality","question":"What are the parameter count and line length limits in EK9?","url":"https://ek9.io/qa/QA0700.html","alternatePhrasings":["What is E11020 too many parameters?","What is E11040 line too long?","How many parameters can a function have?","What is the maximum line length in EK9?"],"answer":"EK9 enforces both parameter count limits and line length limits to maintain readable, maintainable code.\n\nPARAMETER COUNT (E11020)\nFunctions and methods with excessive parameters trigger E11020. The compiler scores parameters by complexity, with Booleans and similar types counting more heavily.\n\nLINE LENGTH (E11040)\nLines exceeding 120 characters trigger E11040. This ensures code is readable without horizontal scrolling.\n\nCORRECT PATTERNS\n1. Group related parameters into a record or class\n2. Use builder or configuration patterns\n3. Split long expressions across multiple lines\n4. Extract intermediate values into named variables\n\nPARAMETER GROUPING\nInstead of many individual parameters:\n  // Bad: 7 parameters\n  createOrder(name, addr, city, zip, item, qty, price)\n  // Good: grouped into meaningful types\n  createOrder(customer as Customer, orderItem as OrderItem)\n\nLINE SPLITTING\nUse string interpolation and intermediate variables to keep lines short.\n\nSee Q316 for complexity metrics. See Q696 for complexity limits. See Q694 for named arguments.","ek9Example":"defines module qa.codequality.parametercountlimits\n\n  defines record\n\n    <?-\n      Group related data into a record.\n      This reduces parameter count in function signatures.\n    -?>\n    ShippingAddress\n      streetAddress as String?\n      cityName as String?\n      postalCode as String?\n\n      default private ShippingAddress()\n\n      ShippingAddress()\n        ->\n          streetAddress as String\n          cityName as String\n          postalCode as String\n        this.streetAddress :=? streetAddress\n        this.cityName :=? cityName\n        this.postalCode :=? postalCode\n\n      default operator $\n      default operator ?\n\n  defines function\n\n    <?-\n      Correct: 2 parameters instead of 6.\n      Related data grouped into records.\n    -?>\n    formatShipping() as pure\n      ->\n        customerName as String\n        shippingAddress as ShippingAddress\n      <- label as String: customerName\n\n      if shippingAddress?\n        label: `${customerName}: ${shippingAddress}`\n\n    <?-\n      Correct: 3 parameters, within limits.\n      Each parameter serves a distinct purpose.\n    -?>\n    calculateDiscount() as pure\n      ->\n        originalPrice as Float\n        discountRate as Float\n        minimumPrice as Float\n      <- finalPrice as Float: originalPrice\n\n      discountedPrice <- originalPrice * (1.0 - discountRate)\n\n      if discountedPrice >= minimumPrice\n        finalPrice: discountedPrice\n\n  defines program\n\n    ParameterCountLimitsDemo()\n      stdout <- Stdout()\n\n      addr <- ShippingAddress(\"123 Main St\", \"Springfield\", \"62701\")\n      label <- formatShipping(\"Alice\", addr)\n      stdout.println(label)\n\n      price <- calculateDiscount(100.0, 0.2, 10.0)\n      stdout.println(`Final price: ${price}`)","migrationContext":"Java: no parameter count enforcement (only static analysis). Python: no enforcement. C++: no enforcement. Rust: Clippy warns about parameter count. Go: no enforcement. EK9: compile error for excessive parameters and line length.","keywords":["E11020","E11040","clean-code","count","length","limit","line","metric","parameter","quality","readable"],"primaryTopics":[],"typicalErrors":[{"error":"E50060","correct":"label <- formatShipping(\"Alice\", addr)","incorrect":"label <- formatShipping(\"Alice\", addr).toUpperCase()","explanation":"String has no toUpperCase() method in EK9. Use upperCase() instead. See ek9 -h E50060 for details."},{"error":"E50060","correct":"stdout.println(`Final price: ${price}`)","incorrect":"stdout.println(price.toString())","explanation":"Float has no toString() method; use the '$' prefix operator or '${...}' interpolation to convert to String. See ek9 -h E50060 for details."}],"companions":[]}
{"id":701,"category":"Security and Sanitization","question":"What is the Sensitive type in EK9?","url":"https://ek9.io/qa/QA0701.html","alternatePhrasings":["How does EK9 protect secret values at runtime?","How do I prevent secrets from being logged in EK9?","What is EK9's equivalent of a secret wrapper?"],"answer":"The Sensitive type is a built-in type that wraps secret values with automatic protection against accidental exposure.\n\nAUTO-REDACTION\nThe $ and $$ operators on a set Sensitive always return '***REDACTED***'. This means secrets cannot leak through:\n  stdout.println($apiKey)       Shows ***REDACTED***\n  msg <- `Key: ${apiKey}`       Interpolates as ***REDACTED***\n  jsonField: $apiKey             Serializes as ***REDACTED***\n\nCONTROLLED CONSTRUCTION\nThe only way to create a set Sensitive is EnvVars.sensitiveGet():\n  env <- EnvVars()\n  secret <- env.sensitiveGet(\"API_KEY\")\nThere is no String constructor visible to EK9 code. Sensitive() creates an unset value.\n\nCONSTANT-TIME EQUALITY\nThe == and <> operators use MessageDigest.isEqual() internally to prevent timing attacks on secret comparison.\n\nCOPY SUPPORT\nThe :=: copy operator transfers secret values between Sensitive variables.\n\nSee Q702 for sensitiveGet() patterns. See Q703 for the Privileged trait and reveal(). See Q704 for compile-time secret detection. See Q218 for security best practices. See Q272 for defense in depth.","ek9Example":"defines module qa.security.sensitivetype\n\n  defines class\n\n    <?-\n      A class without Privileged cannot call reveal().\n      It can receive, store, compare, and redact Sensitive values.\n    -?>\n    SecretLogger\n\n      logRedacted()\n        -> credential as Sensitive\n\n        stdout <- Stdout()\n        if credential?\n          //$ always returns \"***REDACTED***\" - safe to log\n          stdout.println(\"Credential present: \" + $credential)\n        else\n          stdout.println(\"No credential provided\")\n\n      default operator ?\n\n  defines function\n\n    testDefaultConstructor()\n      stdout <- Stdout()\n\n      //Default constructor creates an unset Sensitive\n      secret <- Sensitive()\n\n      if not secret?\n        stdout.println(\"Default Sensitive is unset\")\n\n    testSensitiveGet()\n      stdout <- Stdout()\n      env <- EnvVars()\n\n      //sensitiveGet() is the ONLY way to create a set Sensitive\n      apiKey <- env.sensitiveGet(\"API_KEY\")\n\n      if apiKey?\n        //Promotes to ***REDACTED*** automatically\n        stdout.println(\"Got key (redacted): \" + apiKey)\n      else\n        stdout.println(\"API_KEY not configured\")\n\n    testRedactionInInterpolation()\n      stdout <- Stdout()\n      env <- EnvVars()\n\n      dbPassword <- env.sensitiveGet(\"DB_PASSWORD\")\n\n      if dbPassword?\n        //Interpolation auto-promotes to ***REDACTED***\n        msg <- `Database password: ${dbPassword}`\n        stdout.println(msg)\n\n    testConstantTimeEquality()\n      stdout <- Stdout()\n      env <- EnvVars()\n\n      key1 <- env.sensitiveGet(\"TEST_KEY\")\n      key2 <- env.sensitiveGet(\"TEST_KEY\")\n\n      if key1? and key2?\n        if key1 == key2\n          stdout.println(\"Keys match (constant-time comparison)\")\n\n    testCopyOperator()\n      stdout <- Stdout()\n      env <- EnvVars()\n\n      original <- env.sensitiveGet(\"SECRET_TOKEN\")\n      backup <- Sensitive()\n\n      if original?\n        backup :=: original\n        if backup?\n          stdout.println(\"Secret copied to backup\")\n\n    testPassToLogger()\n      env <- EnvVars()\n      secret <- env.sensitiveGet(\"API_KEY\")\n      logger <- SecretLogger()\n      if logger?\n        logger.logRedacted(secret)\n\n  defines program\n\n    SensitiveTypeDemo()\n      stdout <- Stdout()\n      stdout.println(\"Sensitive type demonstrations\")\n      testDefaultConstructor()\n      testSensitiveGet()\n      testRedactionInInterpolation()\n      testConstantTimeEquality()\n      testCopyOperator()\n      testPassToLogger()","migrationContext":"Java: No language-level secret type. Secrets stored as plain Strings can be logged, serialized, or leaked through any code path. Libraries like Vault provide runtime wrappers. Python: No secret type; plain strings with no redaction. Rust: secrecy crate provides Secret<T> but any code can call expose_secret() with no restriction. Go: No secret type; plain strings. EK9: Built-in Sensitive type with automatic redaction, no String constructor, constant-time equality, and trait-gated reveal().","keywords":["credential","leak","log","password","protect","redact","runtime","safe","secret","sensitive","token","type","wrap"],"primaryTopics":["sensitive type","secret","credential","password"],"typicalErrors":[{"error":"E50060","correct":"secret <- Sensitive()","incorrect":"secret <- Sensitive(\"my-api-key\")","explanation":"Sensitive has no String constructor visible to EK9 code. The only way to create a set Sensitive is via EnvVars.sensitiveGet(). See ek9 -h E50060 for details."},{"error":"E08090","correct":"apiKey <- env.sensitiveGet(\"API_KEY\")","incorrect":"apiKey <- \"AKIAIOSFODNN7EXAMPLE1\"","explanation":"Hardcoded cloud provider credentials are detected at compile time. Use EnvVars.sensitiveGet() to load secrets from environment variables instead. See ek9 -h E08090 for details."},{"error":"E50060","correct":"original <- env.sensitiveGet(\"SECRET_TOKEN\")","incorrect":"original <- \"sk_test_abcdefghijklmnopqrstuvwxyz\"","explanation":"Hardcoded API keys are detected at compile time. Load API keys from environment variables using EnvVars.sensitiveGet(). See ek9 -h E50060 for details."},{"error":"E08090","correct":"dbPassword <- env.sensitiveGet(\"DB_PASSWORD\")","incorrect":"dbPassword <- \"postgres://admin:s3cret@db.example.com/prod\"","explanation":"Database URLs with embedded passwords are detected at compile time. Store the connection string in an environment variable. See ek9 -h E08090 for details."}],"companions":[]}
{"id":702,"category":"Security and Sanitization","question":"How do I load secrets from environment variables in EK9?","url":"https://ek9.io/qa/QA0702.html","alternatePhrasings":["What is sensitiveGet() in EK9?","How do I use EnvVars to load credentials safely?","What is the difference between get() and sensitiveGet()?"],"answer":"Use EnvVars.sensitiveGet() to load secret values as Sensitive type. This is the ONLY way to create a set Sensitive value in EK9.\n\nBASIC PATTERN\n  env <- EnvVars()\n  apiKey <- env.sensitiveGet(\"API_KEY\")\n  if apiKey?\n    //Key is loaded and protected\n\nGUARD PATTERN\nCombine sensitiveGet() with guard expressions for clean handling:\n  if dbPassword <- env.sensitiveGet(\"DB_PASSWORD\")\n    connectDatabase(dbPassword)\n  else\n    stderr.println(\"DB_PASSWORD not configured\")\n\nGET vs SENSITIVEGET\n  env.get(name)            Returns String, auto-sanitized against injection\n  env.sensitiveGet(name)   Returns Sensitive, auto-redacting wrapper\n\nUse get() for configuration values (paths, URLs, feature flags).\nUse sensitiveGet() for secrets (passwords, API keys, tokens).\n\nMULTIPLE SECRETS\n  env <- EnvVars()\n  dbHost <- env.get(\"DB_HOST\")\n  dbUser <- env.get(\"DB_USER\")\n  dbPass <- env.sensitiveGet(\"DB_PASSWORD\")\n\nConfiguration values use get(), only the password uses sensitiveGet().\n\nSee Q701 for the Sensitive type overview. See Q703 for Privileged reveal(). See Q704 for compile-time secret detection. See Q271 for EnvVars get() patterns.","ek9Example":"defines module qa.security.sensitiveget\n\n  defines function\n\n    <?-\n      Basic pattern: load a secret from an environment variable.\n    -?>\n    testBasicSensitiveGet()\n      stdout <- Stdout()\n      env <- EnvVars()\n\n      apiKey <- env.sensitiveGet(\"API_KEY\")\n\n      if apiKey?\n        stdout.println(\"API key loaded (redacted): \" + apiKey)\n      else\n        stdout.println(\"API_KEY not set in environment\")\n\n    <?-\n      Guard pattern: combine sensitiveGet with guard expression.\n    -?>\n    testGuardPattern()\n      stdout <- Stdout()\n      stderr <- Stderr()\n      env <- EnvVars()\n\n      if dbPassword <- env.sensitiveGet(\"DB_PASSWORD\")\n        stdout.println(\"Database password loaded\")\n      else\n        stderr.println(\"DB_PASSWORD not configured\")\n\n    <?-\n      Compare get() vs sensitiveGet() for different use cases.\n    -?>\n    testGetVsSensitiveGet()\n      stdout <- Stdout()\n      env <- EnvVars()\n\n      //get() for configuration values\n      if dbHost <- env.get(\"DB_HOST\")\n        stdout.println(\"DB host: \" + dbHost)\n\n      //sensitiveGet() for secrets\n      dbPass <- env.sensitiveGet(\"DB_PASSWORD\")\n      if dbPass?\n        //Auto-promotes to ***REDACTED***\n        stdout.println(\"DB password: \" + dbPass)\n\n    <?-\n      Loading multiple secrets from environment.\n    -?>\n    testMultipleSecrets()\n      stdout <- Stdout()\n      env <- EnvVars()\n\n      //Configuration uses get()\n      region <- env.get(\"AWS_REGION\")\n\n      //Secrets use sensitiveGet()\n      accessKey <- env.sensitiveGet(\"AWS_ACCESS_KEY_ID\")\n      secretKey <- env.sensitiveGet(\"AWS_SECRET_ACCESS_KEY\")\n\n      if region?\n        stdout.println(\"Region: \" + region)\n\n      if accessKey? and secretKey?\n        stdout.println(\"AWS credentials loaded\")\n\n    <?-\n      Safe comparison of two secrets using constant-time equality.\n    -?>\n    testSecretComparison()\n      stdout <- Stdout()\n      env <- EnvVars()\n\n      expected <- env.sensitiveGet(\"EXPECTED_TOKEN\")\n      provided <- env.sensitiveGet(\"PROVIDED_TOKEN\")\n\n      if expected? and provided?\n        if expected == provided\n          stdout.println(\"Tokens match\")\n        else\n          stdout.println(\"Tokens do not match\")\n\n  defines program\n\n    SensitiveGetDemo()\n      stdout <- Stdout()\n      stdout.println(\"sensitiveGet() demonstrations\")\n      testBasicSensitiveGet()\n      testGuardPattern()\n      testGetVsSensitiveGet()\n      testMultipleSecrets()\n      testSecretComparison()","migrationContext":"Java: System.getenv() returns raw String with no protection class. Secrets are indistinguishable from configuration. Python: os.environ returns raw strings. Go: os.Getenv returns raw string. Rust: std::env::var returns raw string. All these allow secrets to be logged, serialized, or leaked. EK9: sensitiveGet() returns Sensitive type that auto-redacts on any string conversion.","keywords":["configuration","connect","credential","database","environment","envvars","guard","load","password","runtime","safe","secret","sensitiveGet","service","token","url","username"],"primaryTopics":[],"typicalErrors":[{"error":"E11080","correct":"accessKey <- env.sensitiveGet(\"AWS_ACCESS_KEY_ID\")","incorrect":"accessKey <- \"AKIAIOSFODNN7EXAMPLE1\"","explanation":"AWS access keys must not be hardcoded. Load them from environment variables using sensitiveGet(). See ek9 -h E11080 for details."},{"error":"E11081","correct":"expected <- env.sensitiveGet(\"EXPECTED_TOKEN\")","incorrect":"expected <- \"ghp_ABCDEFabcdef1234567890abcdef12345678\"","explanation":"GitHub personal access tokens must not be hardcoded. Load them from environment variables using sensitiveGet(). See ek9 -h E11081 for details."},{"error":"E11083","correct":"dbPass <- env.sensitiveGet(\"DB_PASSWORD\")","incorrect":"dbPass <- \"postgres://admin:s3cret@db.example.com/prod\"","explanation":"Database URLs with embedded passwords are detected at compile time. Store the connection string in an environment variable. See ek9 -h E11083 for details."},{"error":"E11084","correct":"provided <- env.sensitiveGet(\"PROVIDED_TOKEN\")","incorrect":"provided <- \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.dozjgNryP4J3jVmNHl0w5N_XgL0n3I9PlFUP0THsR8U\"","explanation":"JWT tokens must not be hardcoded in source code. Generate tokens at runtime or load from environment variables. See ek9 -h E11084 for details."}],"companions":[]}
{"id":703,"category":"Security and Sanitization","question":"How do I access the raw value of a Sensitive in EK9?","url":"https://ek9.io/qa/QA0703.html","alternatePhrasings":["What is the Privileged trait in EK9?","How does reveal() work on Sensitive values?","How do I unwrap a secret when I need the actual value?"],"answer":"The reveal() method on Sensitive returns the raw secret value as a String. It is gated by the Privileged trait — only classes with 'with trait of Privileged' can call reveal().\n\nPRIVILEGED TRAIT\nThe Privileged trait is a built-in marker trait with no methods. It acts as a compile-time gate:\n  HttpClient with trait of Privileged\n    sendRequest()\n      -> apiKey as Sensitive\n      header <- apiKey.reveal()\n\nWHY GATED?\nMost code should never need the raw secret. Sensitive values should flow through the system redacted. Only infrastructure code (HTTP clients, database drivers, encryption) needs the actual value. The Privileged trait makes this boundary explicit and auditable.\n\nWHO CANNOT CALL REVEAL?\n  - Classes without the Privileged trait → E11090\n  - Functions (cannot have traits) → E11090\n  - Dynamic classes without Privileged → E11090\n\nWHO CAN CALL REVEAL?\n  - Regular classes with 'with trait of Privileged'\n  - Named dynamic classes with 'trait of Privileged'\n  - Unnamed dynamic classes with 'trait of Privileged'\n\nAUDIT TRAIL\nSearching the codebase for 'Privileged' identifies every point where secrets can be exposed. This makes security audits trivial compared to languages where any code can access secret values.\n\nSee Q701 for the Sensitive type overview. See Q702 for sensitiveGet() patterns. See Q704 for compile-time secret detection.","ek9Example":"defines module qa.security.privilegedreveal\n\n  defines trait\n\n    SecretConsumer\n      consumeSecret() as abstract\n        -> secret as Sensitive\n\n  defines class\n\n    <?-\n      A class WITH Privileged can call reveal() to access raw secret value.\n      Only infrastructure code like HTTP clients should do this.\n    -?>\n    HttpClient with trait of Privileged\n\n      sendAuthenticatedRequest()\n        -> token as Sensitive\n\n        rawToken <- token.reveal()\n        stdout <- Stdout()\n        if rawToken?\n          header <- `Bearer ${rawToken}`\n          stdout.println(`Auth header set: ${length header} chars`)\n\n    <?-\n      A class WITHOUT Privileged can safely handle Sensitive values\n      using redaction, comparison, and copy - but NOT reveal().\n    -?>\n    SafeLogger\n\n      logActivity()\n        -> credential as Sensitive\n\n        stdout <- Stdout()\n        if credential?\n          //$ always returns ***REDACTED*** - safe to log\n          stdout.println(\"Activity with credential: \" + $credential)\n        else\n          stdout.println(\"No credential for activity\")\n\n      default operator ?\n\n  defines function\n\n    <?-\n      Named dynamic class with Privileged can call reveal().\n    -?>\n    testNamedDynamicReveal()\n      env <- EnvVars()\n      secret <- env.sensitiveGet(\"DB_PASSWORD\")\n\n      extractor <- PasswordExtractor(secret) trait of Privileged as class\n        getPassword()\n          <- pwd as String?\n          pwd: secret.reveal()\n        default operator ?\n\n      if extractor?\n        result <- extractor.getPassword()\n        stdout <- Stdout()\n        if result?\n          stdout.println(`Password extracted: ${length result} chars`)\n\n    <?-\n      Test that non-Privileged code can still work with Sensitive values.\n    -?>\n    testSafeOperations()\n      stdout <- Stdout()\n      env <- EnvVars()\n\n      secret <- env.sensitiveGet(\"API_KEY\")\n\n      //All safe operations - no Privileged needed\n      if secret?\n        //Auto-promotes to ***REDACTED*** in string context\n        stdout.println(\"Redacted: \" + secret)\n\n      backup <- Sensitive()\n      if secret?\n        backup :=: secret\n        if backup?\n          stdout.println(\"Backup created successfully\")\n\n    testPrivilegedReveal()\n      env <- EnvVars()\n      token <- env.sensitiveGet(\"AUTH_TOKEN\")\n      client <- HttpClient()\n      if client? and token?\n        client.sendAuthenticatedRequest(token)\n\n    testSafeLogging()\n      env <- EnvVars()\n      credential <- env.sensitiveGet(\"SERVICE_KEY\")\n      logger <- SafeLogger()\n      if logger?\n        logger.logActivity(credential)\n\n  defines program\n\n    PrivilegedRevealDemo()\n      stdout <- Stdout()\n      stdout.println(\"Privileged trait and reveal() demonstrations\")\n      testNamedDynamicReveal()\n      testSafeOperations()\n      testPrivilegedReveal()\n      testSafeLogging()","migrationContext":"Java: No access control on secret values. Any code with a String reference can print, log, or serialize it. Rust: secrecy crate's expose_secret() can be called by any code — no trait gating. Python: No secret type at all. Go: No secret type. EK9: reveal() is compile-time gated by the Privileged trait. The compiler rejects reveal() calls from non-Privileged contexts.","keywords":["access","audit","gate","infrastructure","privileged","raw","reveal","secret","security","sensitive","trait","unwrap"],"primaryTopics":[],"typicalErrors":[{"error":"E11090","correct":"HttpClient with trait of Privileged","incorrect":"HttpClient","explanation":"Only classes with the Privileged trait can call reveal() on Sensitive values. Removing 'with trait of Privileged' causes the reveal() call inside sendAuthenticatedRequest() to fail. See ek9 -h E11090 for details."},{"error":"E11090","correct":"PasswordExtractor(secret) trait of Privileged as class","incorrect":"PasswordExtractor(secret) as class","explanation":"Dynamic classes also need the Privileged trait to call reveal(). Removing 'trait of Privileged' causes the reveal() call inside getPassword() to fail. See ek9 -h E11090 for details."},{"error":"E50060","correct":"secret <- env.sensitiveGet(\"API_KEY\")","incorrect":"secret <- \"sk_test_abcdefghijklmnopqrstuvwxyz\"","explanation":"Hardcoded API keys are detected at compile time. Load API keys from environment variables using sensitiveGet(). See ek9 -h E50060 for details."},{"error":"E50060","correct":"token <- env.sensitiveGet(\"AUTH_TOKEN\")","incorrect":"token <- \"-----BEGIN RSA PRIVATE KEY-----\"","explanation":"Private key material must not be hardcoded in source code. Load private keys from environment variables or files at runtime. See ek9 -h E50060 for details."}],"companions":[]}
{"id":704,"category":"Security and Sanitization","question":"How does EK9 detect hardcoded secrets at compile time?","url":"https://ek9.io/qa/QA0704.html","alternatePhrasings":["What secret patterns does the EK9 compiler catch?","Does EK9 have SAST for credentials?","How do I fix a hardcoded secret error in EK9?"],"answer":"The EK9 compiler scans ALL string literals (including interpolated strings) for known secret patterns during the PRE_IR_CHECKS phase. Detection is automatic — no configuration needed.\n\nDETECTED PATTERNS\n  E11080  Cloud provider keys (AWS AKIA..., GCP AIza..., Azure keys)\n  E11081  Platform tokens (GitHub ghp_, GitLab glpat-, Slack xoxb-)\n  E11082  Private key material (-----BEGIN RSA PRIVATE KEY-----)\n  E11083  Database URLs with passwords (postgres://user:pass@host)\n  E11084  JWT tokens (eyJ... three-part base64 structure)\n  E11086  API keys (Stripe sk_test_, Anthropic sk-ant-, OpenAI sk-)\n\nINTERPOLATED STRINGS\nSecrets inside backtick interpolation are also detected:\n  msg <- `Key: ${\"sk_test_abc123def456ghi789jkl\"}`   CAUGHT\n\nHOW TO FIX\nReplace hardcoded secrets with environment variable lookups:\n  env <- EnvVars()\n  stripeKey <- env.sensitiveGet(\"STRIPE_SECRET_KEY\")\n\nFor non-secret configuration, use env.get() instead.\n\nINTEGRATED SECURITY\nUnlike external SAST tools (GitGuardian, TruffleHog, detect-secrets), EK9's detection is built into the compiler. It cannot be bypassed, skipped, or misconfigured. If it compiles, there are no hardcoded secrets.\n\nSee Q701 for the Sensitive type. See Q702 for sensitiveGet() patterns. See Q703 for Privileged reveal(). See Q272 for defense in depth.","ek9Example":"defines module qa.security.secretdetection\n\n  defines function\n\n    <?-\n      Correct pattern: load secrets from environment variables.\n    -?>\n    testCorrectSecretLoading()\n      stdout <- Stdout()\n      env <- EnvVars()\n\n      //Cloud provider keys loaded safely\n      awsKey <- env.sensitiveGet(\"AWS_ACCESS_KEY_ID\")\n      if awsKey?\n        stdout.println(\"AWS key loaded: \" + awsKey)\n\n      //Platform tokens loaded safely\n      ghToken <- env.sensitiveGet(\"GITHUB_TOKEN\")\n      if ghToken?\n        stdout.println(\"GitHub token loaded: \" + ghToken)\n\n      //API keys loaded safely\n      stripeKey <- env.sensitiveGet(\"STRIPE_SECRET_KEY\")\n      if stripeKey?\n        stdout.println(\"Stripe key loaded: \" + stripeKey)\n\n    <?-\n      Database connection without embedded password.\n    -?>\n    testSafeDatabaseConnection()\n      stdout <- Stdout()\n      env <- EnvVars()\n\n      //Host and database name are configuration, not secrets\n      dbHost <- env.get(\"DB_HOST\")\n      dbName <- env.get(\"DB_NAME\")\n\n      //Password is a secret\n      dbPassword <- env.sensitiveGet(\"DB_PASSWORD\")\n\n      if dbHost? and dbName? and dbPassword?\n        stdout.println(\"Database connection configured\")\n\n    <?-\n      Safe string literals that should NOT trigger detection.\n    -?>\n    testSafeStrings()\n      stdout <- Stdout()\n\n      //Normal content - no secret patterns\n      greeting <- \"Hello, World!\"\n      configPath <- \"/etc/app/config.yaml\"\n      contentType <- \"application/json\"\n      apiEndpoint <- \"https://api.example.com/v1/users\"\n\n      stdout.println(greeting)\n      stdout.println(configPath)\n      stdout.println(contentType)\n      stdout.println(apiEndpoint)\n\n    <?-\n      Safe database URLs without passwords.\n    -?>\n    testSafeDatabaseUrls()\n      stdout <- Stdout()\n\n      //URLs without credentials are safe\n      localDb <- \"postgres://localhost/mydb\"\n      mysqlDb <- \"mysql://localhost:3306/app\"\n\n      stdout.println(localDb)\n      stdout.println(mysqlDb)\n\n  defines program\n\n    SecretDetectionDemo()\n      stdout <- Stdout()\n      stdout.println(\"Compile-time secret detection demonstrations\")\n      testCorrectSecretLoading()\n      testSafeDatabaseConnection()\n      testSafeStrings()\n      testSafeDatabaseUrls()","migrationContext":"Java: No built-in secret detection. Relies on external tools like GitGuardian, TruffleHog, or pre-commit hooks — all optional and bypassable. Python: Same — external tools only. Go: gosec provides some detection but is optional. Rust: No built-in detection. JavaScript: No built-in detection. EK9: Secret detection is part of the compiler. Six pattern families detected automatically. Cannot be bypassed.","keywords":["automatic","aws","cloud","compile","credential","database","detection","github","hardcoded","jwt","key","pattern","private","sast","secret","stripe"],"primaryTopics":[],"typicalErrors":[{"error":"E11080","correct":"awsKey <- env.sensitiveGet(\"AWS_ACCESS_KEY_ID\")","incorrect":"awsKey <- \"AKIAIOSFODNN7EXAMPLE1\"","explanation":"AWS access keys starting with 'AKIA' are detected at compile time. Load cloud provider keys from environment variables. See ek9 -h E11080 for details."},{"error":"E11081","correct":"ghToken <- env.sensitiveGet(\"GITHUB_TOKEN\")","incorrect":"ghToken <- \"ghp_ABCDEFabcdef1234567890abcdef12345678\"","explanation":"GitHub personal access tokens with the 'ghp_' prefix are detected at compile time. Store platform tokens in environment variables. See ek9 -h E11081 for details."},{"error":"E11086","correct":"stripeKey <- env.sensitiveGet(\"STRIPE_SECRET_KEY\")","incorrect":"stripeKey <- \"sk_test_abcdefghijklmnopqrstuvwxyz\"","explanation":"Stripe API keys with the 'sk_test_' prefix are detected at compile time. Load API keys from environment variables. See ek9 -h E11086 for details."},{"error":"E11083","correct":"dbPassword <- env.sensitiveGet(\"DB_PASSWORD\")","incorrect":"dbPassword <- \"postgres://admin:s3cret@db.example.com/prod\"","explanation":"Database URLs with embedded passwords are detected at compile time. Store database URLs in environment variables. See ek9 -h E11083 for details."},{"error":"E11082","correct":"greeting <- \"Hello, World!\"","incorrect":"greeting <- \"-----BEGIN OPENSSH PRIVATE KEY-----\"","explanation":"Private key material including OpenSSH keys is detected at compile time. Load private keys from files or environment variables at runtime. See ek9 -h E11082 for details."},{"error":"E11084","correct":"configPath <- \"/etc/app/config.yaml\"","incorrect":"configPath <- \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoiYWRtaW4ifQ.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ\"","explanation":"JWT tokens with the characteristic three-part base64 structure are detected at compile time. Generate tokens dynamically or load from environment variables. See ek9 -h E11084 for details."}],"companions":[]}
{"id":705,"category":"Security and Sanitization","question":"How do I migrate from hardcoded secrets to secure patterns in EK9?","url":"https://ek9.io/qa/QA0705.html","alternatePhrasings":["How do I fix hardcoded credential errors in EK9?","What is the EK9 pattern for replacing inline secrets?","How do I move from hardcoded passwords to environment variables in EK9?"],"answer":"When the EK9 compiler detects a hardcoded secret (E11080-E11086), follow this migration pattern:\n\nSTEP 1: IDENTIFY THE SECRET TYPE\nThe error code tells you what was detected:\n  E11080 → Cloud provider key (AWS, GCP, Azure)\n  E11081 → Platform token (GitHub, GitLab, Slack)\n  E11082 → Private key material (RSA, ECDSA, OpenSSH)\n  E11083 → Database URL with password\n  E11084 → JWT token\n  E11086 → API key (Stripe, OpenAI, etc.)\n\nSTEP 2: CHOOSE AN ENVIRONMENT VARIABLE NAME\nUse a descriptive, uppercase name with underscores:\n  AWS_ACCESS_KEY_ID, STRIPE_SECRET_KEY, DB_PASSWORD\n\nSTEP 3: REPLACE WITH SENSITIVEGET\n  BEFORE: key <- \"AKIAIOSFODNN7EXAMPLE1\"\n  AFTER:  env <- EnvVars()\n          key <- env.sensitiveGet(\"AWS_ACCESS_KEY_ID\")\n\nSTEP 4: ADD GUARD FOR MISSING VALUES\n  if key <- env.sensitiveGet(\"AWS_ACCESS_KEY_ID\")\n    configureAws(key)\n  else\n    stderr.println(\"AWS_ACCESS_KEY_ID not set\")\n\nSTEP 5: SET ENVIRONMENT VARIABLE\n  export AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE1\n  Or use your platform's secrets management.\n\nDATABASE URL MIGRATION\n  BEFORE: url <- \"postgres://admin:pass@host/db\"\n  AFTER:  url <- env.sensitiveGet(\"DATABASE_URL\")\n\nJWT TOKEN MIGRATION\n  BEFORE: token <- \"eyJhbGci...\"\n  AFTER:  token <- env.sensitiveGet(\"AUTH_TOKEN\")\n  Or generate tokens dynamically at runtime.\n\nSee Q701 for Sensitive type. See Q702 for sensitiveGet(). See Q704 for detection details.","ek9Example":"defines module qa.security.credentialmigration\n\n  defines function\n\n    <?-\n      Correct pattern: cloud provider credential migration.\n    -?>\n    testCloudCredentialMigration()\n      stdout <- Stdout()\n      stderr <- Stderr()\n      env <- EnvVars()\n\n      //AFTER: Load from environment variable\n      if awsKey <- env.sensitiveGet(\"AWS_ACCESS_KEY_ID\")\n        stdout.println(\"AWS key loaded: \" + awsKey)\n      else\n        stderr.println(\"Set AWS_ACCESS_KEY_ID environment variable\")\n\n    <?-\n      Correct pattern: API key migration.\n    -?>\n    testApiKeyMigration()\n      stdout <- Stdout()\n      stderr <- Stderr()\n      env <- EnvVars()\n\n      //AFTER: Load from environment variable\n      if stripeKey <- env.sensitiveGet(\"STRIPE_SECRET_KEY\")\n        stdout.println(\"Stripe key loaded: \" + stripeKey)\n      else\n        stderr.println(\"Set STRIPE_SECRET_KEY environment variable\")\n\n    <?-\n      Correct pattern: database URL migration.\n    -?>\n    testDatabaseUrlMigration()\n      stdout <- Stdout()\n      stderr <- Stderr()\n      env <- EnvVars()\n\n      //AFTER: Separate configuration from secrets\n      dbHost <- env.get(\"DB_HOST\")\n      dbName <- env.get(\"DB_NAME\")\n      dbPassword <- env.sensitiveGet(\"DB_PASSWORD\")\n\n      if dbHost? and dbName? and dbPassword?\n        stdout.println(\"Database configured with separate components\")\n      else\n        stderr.println(\"Configure DB_HOST, DB_NAME, and DB_PASSWORD\")\n\n    <?-\n      Correct pattern: JWT token migration (generate at runtime).\n    -?>\n    testJwtTokenMigration()\n      stdout <- Stdout()\n      stderr <- Stderr()\n      env <- EnvVars()\n\n      //For test tokens: load from environment\n      if testToken <- env.sensitiveGet(\"TEST_JWT_TOKEN\")\n        stdout.println(\"Test JWT loaded: \" + testToken)\n      else\n        stderr.println(\"Set TEST_JWT_TOKEN for testing\")\n\n    <?-\n      Correct pattern: private key migration.\n    -?>\n    testPrivateKeyMigration()\n      stdout <- Stdout()\n      stderr <- Stderr()\n      env <- EnvVars()\n\n      //AFTER: Load key path or content from environment\n      if keyPath <- env.get(\"TLS_KEY_PATH\")\n        stdout.println(\"TLS key path: \" + keyPath)\n      else\n        stderr.println(\"Set TLS_KEY_PATH environment variable\")\n\n  defines program\n\n    CredentialMigrationDemo()\n      stdout <- Stdout()\n      stdout.println(\"Credential migration demonstrations\")\n      testCloudCredentialMigration()\n      testApiKeyMigration()\n      testDatabaseUrlMigration()\n      testJwtTokenMigration()\n      testPrivateKeyMigration()","migrationContext":"Java: Secrets are typically moved from source to properties files (still in repo), then to environment variables, then to Vault/AWS Secrets Manager. Multiple migration steps. Python: Similar journey from .env files to proper secrets management. EK9: The compiler forces the first migration step (source → environment variable) automatically. No optional intermediate steps.","keywords":["connect","credential","database","detection","driver","environment","error","fix","hardcoded","migrate","password","pattern","refactor","replace","secret","service","url"],"primaryTopics":[],"typicalErrors":[{"error":"E08090","correct":"awsKey <- env.sensitiveGet(\"AWS_ACCESS_KEY_ID\")","incorrect":"awsKey <- \"AKIAIOSFODNN7EXAMPLE1\"","explanation":"AWS access keys are detected at compile time. Extract the key to an environment variable and load with sensitiveGet(). See ek9 -h E08090 for details."},{"error":"E08090","correct":"stripeKey <- env.sensitiveGet(\"STRIPE_SECRET_KEY\")","incorrect":"stripeKey <- \"sk_test_abcdefghijklmnopqrstuvwxyz\"","explanation":"Stripe API keys with the 'sk_test_' prefix are detected at compile time. Load API keys from environment variables using sensitiveGet(). See ek9 -h E08090 for details."},{"error":"E08090","correct":"testToken <- env.sensitiveGet(\"TEST_JWT_TOKEN\")","incorrect":"testToken <- \"ghp_ABCDEFabcdef1234567890abcdef12345678\"","explanation":"GitHub personal access tokens with the 'ghp_' prefix are detected at compile time. Store platform tokens in environment variables. See ek9 -h E08090 for details."},{"error":"E11083","correct":"dbPassword <- env.sensitiveGet(\"DB_PASSWORD\")","incorrect":"dbPassword <- \"postgres://admin:s3cret@db.example.com/prod\"","explanation":"Database URLs with embedded passwords are detected at compile time. Store the connection URL in an environment variable. See ek9 -h E11083 for details."},{"error":"E08090","correct":"keyPath <- env.get(\"TLS_KEY_PATH\")","incorrect":"keyPath <- \"-----BEGIN EC PRIVATE KEY-----\"","explanation":"EC private key material is detected at compile time. Load private keys from files or environment variables at runtime. See ek9 -h E08090 for details."}],"companions":[]}
{"id":706,"category":"Security and Sanitization","question":"How do I use credentials, URLs, usernames and passwords or tokens to connect to databases or services?","url":"https://ek9.io/qa/QA0706.html","alternatePhrasings":["How do I securely configure a database connection in EK9?","How do I pass API keys to an external service in EK9?","How do I prepare credentials for a database driver in EK9?"],"answer":"EK9 separates configuration from secrets using EnvVars:\n  env.get(name)            Non-secret config (URLs, hostnames, ports)\n  env.sensitiveGet(name)   Secrets (passwords, API keys, tokens)\n\nDATABASE CONNECTION PATTERN\n  dbHost <- env.get(\"DB_HOST\")\n  dbPassword <- env.sensitiveGet(\"DB_PASSWORD\")\nConfig as regular Strings, password as Sensitive (auto-redacts if logged).\n\nDRIVER COMPONENT PATTERN\nDriver uses Privileged trait to call reveal() at the connection boundary:\n  DatabaseDriver with trait of Privileged\n    connect()\n      -> password as Sensitive\n      rawPassword <- password.reveal()\nSecrets flow as protected Sensitive values. Only Privileged components access raw values.\n\nSee Q701 for Sensitive type. See Q702 for sensitiveGet(). See Q703 for Privileged/reveal(). See Q112 for services. See Q271 for EnvVars. See Q231 for DI program-application linking. See Q232 for component chains.","ek9Example":"defines module qa.security.credentialpreparation\n\n  defines trait\n\n    <?-\n      Trait for any component that consumes database credentials.\n    -?>\n    DatabaseConsumer\n      connectToDatabase() as abstract\n        ->\n          host as String\n          port as String\n          dbName as String\n          username as String\n          password as Sensitive\n\n  defines class\n\n    <?-\n      A database driver component with the Privileged trait.\n      Only this class can call reveal() to access the raw password\n      at the actual connection boundary.\n    -?>\n    DatabaseDriver with trait of Privileged, DatabaseConsumer\n\n      override connectToDatabase()\n        ->\n          host as String\n          port as String\n          dbName as String\n          username as String\n          password as Sensitive\n\n        stdout <- Stdout()\n\n        //Configuration values are regular Strings - safe to log\n        stdout.println(`Connecting to ${host}:${port}/${dbName} as ${username}`)\n\n        //The password is Sensitive - auto-promotes to ***REDACTED***\n        stdout.println(`Password status: ${password}`)\n\n        //Only here, at the connection boundary, do we reveal the raw value\n        rawPassword <- password.reveal()\n\n        if rawPassword?\n          //This is where the actual database driver call would go\n          //e.g. connection <- jdbcDriver.connect(host, port, dbName, username, rawPassword)\n          stdout.println(\"Database connection established\")\n\n      default operator ?\n\n    <?-\n      A service client component with the Privileged trait.\n      Uses reveal() only at the HTTP request boundary.\n    -?>\n    ServiceClient with trait of Privileged\n\n      callService()\n        ->\n          serviceUrl as String\n          apiKey as Sensitive\n\n        stdout <- Stdout()\n\n        //URL is configuration - safe to log\n        stdout.println(`Calling service at ${serviceUrl}`)\n\n        //API key is Sensitive - auto-promotes to ***REDACTED***\n        stdout.println(`API key status: ${apiKey}`)\n\n        //Reveal only at the HTTP boundary\n        rawKey <- apiKey.reveal()\n\n        if rawKey?\n          //This is where the actual HTTP client call would go\n          //e.g. response <- httpClient.get(serviceUrl, \"Authorization\", \"Bearer \" + rawKey)\n          stdout.println(\"Service call completed\")\n\n      default operator ?\n\n  defines constant\n\n    dbPasswordKey <- \"DB_PASSWORD\"\n\n  defines function\n\n    <?-\n      Demonstrates the full database credential preparation pattern.\n    -?>\n    testDatabaseCredentials()\n      stderr <- Stderr()\n      env <- EnvVars()\n\n      //Configuration values loaded with get() - regular Strings\n      dbHost <- env.get(\"DB_HOST\")\n      dbPort <- env.get(\"DB_PORT\")\n      dbName <- env.get(\"DB_NAME\")\n      dbUser <- env.get(\"DB_USER\")\n\n      //Password loaded with sensitiveGet() - Sensitive type\n      dbPassword <- env.sensitiveGet(dbPasswordKey)\n\n      if dbHost? and dbPort? and dbName? and dbUser? and dbPassword?\n        driver <- DatabaseDriver()\n        if driver?\n          driver.connectToDatabase(host: dbHost, port: dbPort, dbName: dbName, username: dbUser, password: dbPassword)\n      else\n        stderr.println(\"Database not fully configured - check environment variables\")\n\n    <?-\n      Demonstrates the service API credential preparation pattern.\n    -?>\n    testServiceCredentials()\n      stderr <- Stderr()\n      env <- EnvVars()\n\n      //Service URL is configuration\n      serviceUrl <- env.get(\"PAYMENT_API_URL\")\n\n      //API key is a secret\n      apiKey <- env.sensitiveGet(\"PAYMENT_API_KEY\")\n\n      if serviceUrl? and apiKey?\n        client <- ServiceClient()\n        if client?\n          client.callService(serviceUrl, apiKey)\n      else\n        stderr.println(\"Payment service not configured - check environment variables\")\n\n    <?-\n      Shows that credentials remain protected throughout the chain.\n    -?>\n    testCredentialProtection()\n      stdout <- Stdout()\n      env <- EnvVars()\n\n      secret <- env.sensitiveGet(dbPasswordKey)\n\n      if secret?\n        //Safe everywhere - auto-promotes to ***REDACTED***\n        stdout.println(`Direct: ${secret}`)\n        msg <- \"Credential: \" + secret\n        stdout.println(msg)\n\n        //Can compare without revealing\n        other <- env.sensitiveGet(dbPasswordKey)\n        if other?\n          if secret == other\n            stdout.println(\"Credentials match (constant-time)\")\n\n  defines program\n\n    CredentialPreparationDemo()\n      stdout <- Stdout()\n      stdout.println(\"Credential preparation for database and service connections\")\n      testDatabaseCredentials()\n      testServiceCredentials()\n      testCredentialProtection()","migrationContext":"Java: plain Strings for credentials, any code can log/leak. EK9: Sensitive values throughout, only Privileged components can reveal().","keywords":["api","component","configure","connect","connection","credential","database","driver","host","jdbc","key","password","port","prepare","privileged","reveal","sensitive","service","token","url","username"],"primaryTopics":[],"typicalErrors":[{"error":"E50060","correct":"dbPassword <- env.sensitiveGet(dbPasswordKey)","incorrect":"dbPassword <- \"postgres://admin:s3cret@db.example.com/prod\"","explanation":"Database URLs with embedded passwords are detected at compile time. Separate host/port/name configuration from the password and load the password with sensitiveGet(). See ek9 -h E50060 for details."},{"error":"E11090","correct":"DatabaseDriver with trait of Privileged, DatabaseConsumer","incorrect":"DatabaseDriver with trait of DatabaseConsumer","explanation":"The driver component needs the Privileged trait to call reveal() at the connection boundary. Removing Privileged causes the compiler to reject the reveal() call. See ek9 -h E11090 for details."},{"error":"E50060","correct":"apiKey <- env.sensitiveGet(\"PAYMENT_API_KEY\")","incorrect":"apiKey <- \"sk_live_abcdefghijklmnopqrstuvwxyz\"","explanation":"API keys for external services must not be hardcoded. Load them from environment variables using sensitiveGet(). See ek9 -h E50060 for details."},{"error":"E11080","correct":"secret <- env.sensitiveGet(dbPasswordKey)","incorrect":"secret <- \"AKIAIOSFODNN7EXAMPLE1\"","explanation":"Cloud provider credentials must not be hardcoded. Load them from environment variables using sensitiveGet(). See ek9 -h E11080 for details."}],"companions":[]}
{"id":707,"category":"Generics","question":"How do I use Consumer and Acceptor with concrete types?","url":"https://ek9.io/qa/QA0707.html","alternatePhrasings":["How do I create a Consumer of String or Acceptor of Integer?","What is the difference between Consumer and Acceptor in practice?","How do I pass Consumer and Acceptor as function parameters?"],"answer":"Consumer and Acceptor are the void-returning single-parameter function types. They have identical signatures except for purity.\n\nCONSUMER IS PURE\nConsumer of T takes a single parameter 't' and returns nothing. Because it is pure, it cannot call impure functions. Consumer implementations must be side-effect free.\n\nACCEPTOR IS IMPURE\nAcceptor of T has the same signature but is NOT pure, so it CAN have side effects like I/O.\n\nCREATING INSTANCES\nUse dynamic function syntax to create concrete instances:\n  validator <- () is Consumer of String as pure function\n    require t?\nThe body uses 't' — the parameter name defined by Consumer.\nNote: Consumer is pure, so the dynamic function must use 'as pure function'.\n\nUSING AS PARAMETERS\nFunctions can accept Consumer or Acceptor to control what callers can do:\n  processItem()\n    -> item as String, handler as Consumer of String\n    handler(item)\n\nBUILT-IN INTEGRATION\nOptional and Result use both:\n  opt.whenPresent(myConsumer)   // Pure read-only access\n  opt.whenPresent(myAcceptor)   // Can modify state\n\nSee Q54 for pure vs impure concepts. See Q87 for Result operations. See Q55 for passing functions as delegates.","ek9Example":"defines module qa.genericsdeep.consumeracceptor\n\n  defines function\n\n    <?-\n      Helper that accepts a Consumer parameter (pure).\n      Because the handler is a Consumer, this function knows\n      no side effects will occur when it is called.\n    -?>\n    processWithConsumer() as pure\n      ->\n        item as String\n        handler as Consumer of String\n      handler(item)\n\n    <?-\n      Helper that accepts an Acceptor parameter (impure).\n      The Acceptor may perform side effects like I/O.\n    -?>\n    processWithAcceptor()\n      ->\n        item as String\n        handler as Acceptor of String\n      handler(item)\n\n  defines program\n\n    ConsumerAcceptorDemo()\n      stdout <- Stdout()\n\n      //Consumer: pure, read-only — validate without side effects\n      //Body uses 't' — the parameter name from Consumer's signature\n      validator <- () is Consumer of String as pure function\n        require t?\n\n      processWithConsumer(\"Hello\", validator)\n      stdout.println(\"Consumer validation passed\")\n\n      //Acceptor: impure, can have side effects like printing\n      printer <- () is Acceptor of String as function\n        stdout <- Stdout()\n        stdout.println(`Acceptor received: ${t}`)\n\n      processWithAcceptor(\"World\", printer)\n\n      //Both work with Optional via whenPresent\n      opt <- Optional(\"Steve\")\n      if opt?\n        stdout.println(`Optional contains: ${opt.get()}`)","migrationContext":"Java: java.util.function.Consumer<T> — no purity distinction, all consumers can have side effects. Kotlin: (T) -> Unit — no pure/impure variants. Rust: Fn(&T) for immutable borrow, FnMut(&mut T) for mutable — structural not nominal. Go: func(T) — no purity enforcement. C#: Action<T> — no purity concept. EK9: Consumer (pure) vs Acceptor (impure) gives compile-time purity guarantees.","keywords":["acceptor","callback","consumer","delegate","function-type","generic","impure","parameter","pure","side-effect","void"],"primaryTopics":["consumer","acceptor","consumer function type"],"typicalErrors":[{"error":"E05150","correct":"() is Consumer of String as pure function","incorrect":"() is Consumer of String as function","explanation":"Consumer is pure — dynamic functions extending Consumer must use 'as pure function', not just 'as function'. The compiler requires the purity of the implementation to match its pure super type. See ek9 -h E05150 for details."}],"companions":[]}
{"id":708,"category":"Generics","question":"How do I use Supplier and Producer to create values?","url":"https://ek9.io/qa/QA0708.html","alternatePhrasings":["What is the difference between Supplier and Producer in EK9?","How do I create a factory function with Supplier of T?","When should I use Producer instead of Supplier?"],"answer":"Supplier and Producer are the no-parameter function types that return a value. They differ only in purity.\n\nSUPPLIER IS PURE\nSupplier of T takes no parameters and returns 'r' of type T. Being pure, it always returns the same value:\n  factory <- () is Supplier of String as pure function\n    r: \"Default\"\nA pure Supplier is a constant factory — deterministic and safe.\n\nPRODUCER IS IMPURE\nProducer of T has the same signature but is NOT pure, so it can read external state:\n  generator <- () is Producer of String as function\n    r: \"Generated\"\nProducer allows side effects in value creation.\n\nFACTORY PATTERN\nUse Supplier for deterministic factories and Producer for stateful ones:\n  obtainValue()\n    -> factory as Supplier of String\n    <- result as String: factory()\n\nSee Q54 for pure vs impure concepts. See Q649 for generic function implementation rules.","ek9Example":"defines module qa.genericsdeep.supplierproducer\n\n  defines function\n\n    <?-\n      Helper that uses a Supplier to get a value.\n      Pure context — only pure Suppliers can be passed.\n    -?>\n    obtainValue() as pure\n      -> factory as Supplier of String\n      <- result as String: factory()\n\n    <?-\n      Helper that uses a Producer to get a value.\n      Impure context — Producer can have side effects.\n    -?>\n    generateValue()\n      -> factory as Producer of String\n      <- result as String: factory()\n\n  defines program\n\n    SupplierProducerDemo()\n      stdout <- Stdout()\n\n      //Supplier: pure, deterministic factory\n      //Body sets 'r' — the return variable from Supplier's signature\n      nameFactory <- () is Supplier of String as pure function\n        r: \"DefaultName\"\n\n      name <- obtainValue(nameFactory)\n      stdout.println(`Supplier result: ${name}`)\n\n      //Producer: impure, can access external state\n      greetingFactory <- () is Producer of String as function\n        r: \"Hello from Producer\"\n\n      greeting <- generateValue(greetingFactory)\n      stdout.println(`Producer result: ${greeting}`)\n\n      //Direct call on dynamic function variable\n      directResult <- nameFactory()\n      stdout.println(`Direct Supplier: ${directResult}`)","migrationContext":"Java: java.util.function.Supplier<T> — no purity distinction, can have side effects. Kotlin: () -> T — no pure/impure variants. Rust: Fn() -> T (pure) vs FnMut() -> T (stateful) — structural distinction. Go: func() T — no purity enforcement. C#: Func<T> — no purity concept. EK9: Supplier (pure, deterministic) vs Producer (impure, can be stateful) — compile-time enforced.","keywords":["create","factory","function-type","generic","impure","no-parameter","producer","pure","return","side-effect","supplier"],"primaryTopics":["supplier","producer","supplier function type"],"typicalErrors":[{"error":"E05150","correct":"() is Supplier of String as pure function","incorrect":"() is Supplier of String as function","explanation":"Supplier is pure — dynamic functions extending Supplier must use 'as pure function', not just 'as function'. The compiler requires the purity of the implementation to match its pure super type. See ek9 -h E05150 for details."}],"companions":[]}
{"id":709,"category":"Generics","question":"How do I use Predicate and Assessor for testing conditions?","url":"https://ek9.io/qa/QA0709.html","alternatePhrasings":["What is the difference between Predicate and Assessor in EK9?","How do I create a Predicate of Integer for filtering?","When should I use Assessor instead of Predicate?"],"answer":"Predicate and Assessor are the Boolean-returning single-parameter function types. They test a condition on value 't'.\n\nPREDICATE IS PURE\nPredicate of T takes 't' and returns 'r' as Boolean. Being pure, it is a stateless test:\n  check <- () is Predicate of Integer as pure function\n    r: t > 0\nAlways gives the same result for the same input.\n\nASSESSOR IS IMPURE\nAssessor of T has the same signature but is NOT pure:\n  logCheck <- () is Assessor of Integer as function\n    stdout <- Stdout()\n    stdout.println(`Checking: ${t}`)\n    r: t > 0\nAssessor allows side effects during evaluation.\n\nSTREAM FILTER PATTERN\nPredicates work naturally as stream filter conditions.\n\nPARAMETER USAGE\nPass Predicate or Assessor to control purity:\n  testValue() as pure\n    -> item as Integer, check as Predicate of Integer\n    <- result as Boolean: check(item)\n\nSee Q54 for pure vs impure concepts. See Q89 for stream pipeline operations. See Q235 for stream operations reference.","ek9Example":"defines module qa.genericsdeep.predicateassessor\n\n  defines function\n\n    <?-\n      Helper that uses a Predicate to test a value.\n      Pure context — only pure Predicates accepted.\n    -?>\n    testValue() as pure\n      ->\n        item as Integer\n        check as Predicate of Integer\n      <- result as Boolean: check(item)\n\n    <?-\n      Helper that uses an Assessor to test a value.\n      Impure context — Assessor can have side effects.\n    -?>\n    assessValue()\n      ->\n        item as Integer\n        check as Assessor of Integer\n      <- result as Boolean: check(item)\n\n  defines program\n\n    PredicateAssessorDemo()\n      stdout <- Stdout()\n\n      //Predicate: pure, stateless Boolean test\n      //Body uses 't' for input, 'r' for return — from Predicate's signature\n      positiveCheck <- () is Predicate of Integer as pure function\n        r: t > 0\n\n      stdout.println(`Is 42 positive: ${testValue(42, positiveCheck)}`)\n      stdout.println(`Is -1 positive: ${testValue(-1, positiveCheck)}`)\n\n      //Assessor: impure, can log during evaluation\n      logCheck <- () is Assessor of Integer as function\n        stdout <- Stdout()\n        stdout.println(`Assessing: ${t}`)\n        r: t > 0\n\n      result <- assessValue(99, logCheck)\n      stdout.println(`Assessment result: ${result}`)","migrationContext":"Java: java.util.function.Predicate<T> — no purity distinction, can have side effects, supports and/or/negate composition. Kotlin: (T) -> Boolean — no pure/impure variants. Rust: Fn(&T) -> bool (pure) vs FnMut(&T) -> bool (stateful). Go: func(T) bool — no purity. C#: Predicate<T> or Func<T, bool> — no purity concept. EK9: Predicate (pure, stateless test) vs Assessor (impure, can track state) — compile-time enforced.","keywords":["assessor","boolean","condition","filter","function-type","generic","impure","predicate","pure","side-effect","stream","test"],"primaryTopics":["predicate","assessor","predicate function type"],"typicalErrors":[{"error":"E05150","correct":"() is Predicate of Integer as pure function","incorrect":"() is Predicate of Integer as function","explanation":"Predicate is pure — dynamic functions extending Predicate must use 'as pure function', not just 'as function'. The compiler requires the purity of the implementation to match its pure super type. See ek9 -h E05150 for details."}],"companions":[]}
{"id":710,"category":"Generics","question":"How do I use Function and Routine for transformations?","url":"https://ek9.io/qa/QA0710.html","alternatePhrasings":["What is the difference between Function and Routine in EK9?","How do I create a Function of (String, Integer) for mapping?","When should I use Routine instead of Function?"],"answer":"Function and Routine are the two-type-parameter transformation types. They take 't' of type T and return 'r' of type R.\n\nFUNCTION IS PURE\nFunction of (T, R) takes parameter 't' and returns 'r'. Being pure, the transformation is deterministic:\n  mapper <- () is Function of (String, Integer) as pure function\n    r: length t\nAlways produces the same output for the same input.\n\nROUTINE IS IMPURE\nRoutine of (T, R) has the same signature but is NOT pure:\n  logMapper <- () is Routine of (String, Integer) as function\n    stdout <- Stdout()\n    stdout.println(`Transforming: ${t}`)\n    r: length t\nRoutine allows side effects during transformation.\n\nPARAMETER USAGE\nAccept Function for guaranteed pure transforms:\n  transform() as pure\n    -> item as String, mapper as Function of (String, Integer)\n    <- result as Integer: mapper(item)\n\nSee Q54 for pure vs impure concepts. See Q89 for stream pipeline operations. See Q649 for generic function implementation.","ek9Example":"defines module qa.genericsdeep.functionroutine\n\n  defines function\n\n    <?-\n      Helper that applies a pure Function transformation.\n    -?>\n    applyTransform() as pure\n      ->\n        item as String\n        mapper as Function of (String, Integer)\n      <- result as Integer: mapper(item)\n\n    <?-\n      Helper that applies an impure Routine transformation.\n    -?>\n    applyRoutine()\n      ->\n        item as String\n        mapper as Routine of (String, Integer)\n      <- result as Integer: mapper(item)\n\n  defines program\n\n    FunctionRoutineDemo()\n      stdout <- Stdout()\n\n      //Function: pure, deterministic transform\n      //Body uses 't' for input, 'r' for return — from Function's signature\n      lenMapper <- () is Function of (String, Integer) as pure function\n        r: length t\n\n      stdout.println(`Length of 'Hello': ${applyTransform(\"Hello\", lenMapper)}`)\n\n      //Another Function: Integer to String\n      strMapper <- () is Function of (Integer, String) as pure function\n        r: $t\n\n      stdout.println(`42 as string: ${strMapper(42)}`)\n\n      //Routine: impure, can log during transformation\n      logMapper <- () is Routine of (String, Integer) as function\n        stdout <- Stdout()\n        stdout.println(`Transforming: ${t}`)\n        r: length t\n\n      result <- applyRoutine(\"World\", logMapper)\n      stdout.println(`Routine result: ${result}`)","migrationContext":"Java: java.util.function.Function<T,R> — no purity distinction, can have side effects. Kotlin: (T) -> R — no pure/impure variants. Rust: Fn(T) -> R (pure) vs FnMut(T) -> R (stateful). Go: func(T) R — no purity enforcement. C#: Func<T, TResult> — no purity concept. EK9: Function (pure, deterministic transform) vs Routine (impure, can access state) — compile-time enforced.","keywords":["function","function-type","generic","impure","map","mapping","pure","routine","side-effect","transform","two-parameter"],"primaryTopics":[],"typicalErrors":[{"error":"E05150","correct":"() is Function of (Integer, String) as pure function","incorrect":"() is Function of (Integer, String) as function","explanation":"Function is pure — dynamic functions extending Function must use 'as pure function', not just 'as function'. The compiler requires the purity of the implementation to match its pure super type. See ek9 -h E05150 for details."}],"companions":[]}
{"id":711,"category":"Generics","question":"How do I use BiConsumer and BiAcceptor with two parameters?","url":"https://ek9.io/qa/QA0711.html","alternatePhrasings":["What is the difference between BiConsumer and BiAcceptor?","How do I create a BiConsumer of (String, Integer)?","When should I use BiAcceptor instead of BiConsumer?"],"answer":"BiConsumer and BiAcceptor are the void-returning two-parameter function types. They process two values 't' and 'u' without returning a result.\n\nBICONSUMER IS PURE\nBiConsumer of (T, U) takes parameters 't' and 'u' and returns nothing. Being pure, it cannot call impure functions:\n  validator <- () is BiConsumer of (String, Integer) as pure function\n    require t? and u?\n\nBIACCEPTOR IS IMPURE\nBiAcceptor of (T, U) has the same signature but is NOT pure:\n  logger <- () is BiAcceptor of (String, Integer) as function\n    stdout <- Stdout()\n    stdout.println(`Name: ${t}, Age: ${u}`)\n\nUSE CASES\nBiConsumer: Pure validation of two related values.\nBiAcceptor: Logging, formatting, or storing pairs of values.\n\nSee Q54 for pure vs impure concepts. See Q707 for single-parameter Consumer/Acceptor.","ek9Example":"defines module qa.genericsdeep.biconsumerbiacceptor\n\n  defines function\n\n    <?-\n      Helper that validates a pair using a pure BiConsumer.\n    -?>\n    validateWith() as pure\n      ->\n        name as String\n        age as Integer\n        validator as BiConsumer of (String, Integer)\n      validator(name, age)\n\n    <?-\n      Helper that processes a pair using an impure BiAcceptor.\n    -?>\n    processPair()\n      ->\n        name as String\n        age as Integer\n        handler as BiAcceptor of (String, Integer)\n      handler(name, age)\n\n  defines program\n\n    BiConsumerBiAcceptorDemo()\n      stdout <- Stdout()\n\n      //BiConsumer: pure, validation only\n      //Body uses 't' and 'u' — from BiConsumer's signature\n      validator <- () is BiConsumer of (String, Integer) as pure function\n        require t? and u?\n\n      validateWith(\"Steve\", 42, validator)\n      stdout.println(\"BiConsumer validation passed\")\n\n      //BiAcceptor: impure, can log\n      logger <- () is BiAcceptor of (String, Integer) as function\n        stdout <- Stdout()\n        stdout.println(`Name: ${t}, Age: ${u}`)\n\n      processPair(\"Alice\", 30, logger)\n\n      //Direct call\n      validator(\"Bob\", 25)\n      stdout.println(\"Direct BiConsumer call passed\")","migrationContext":"Java: java.util.function.BiConsumer<T,U> — no purity distinction. Kotlin: (T, U) -> Unit — no pure/impure variants. Rust: Fn(&T, &U) vs FnMut(&T, &U) — structural. Go: func(T, U) — no purity. C#: Action<T1, T2> — no purity concept. EK9: BiConsumer (pure) vs BiAcceptor (impure) — compile-time enforced purity.","keywords":["biacceptor","biconsumer","callback","function-type","generic","impure","pair","pure","side-effect","two-parameter","void"],"primaryTopics":[],"typicalErrors":[{"error":"E05150","correct":"() is BiConsumer of (String, Integer) as pure function","incorrect":"() is BiConsumer of (String, Integer) as function","explanation":"BiConsumer is pure — dynamic functions extending BiConsumer must use 'as pure function', not just 'as function'. The compiler requires the purity of the implementation to match its pure super type. See ek9 -h E05150 for details."}],"companions":[]}
{"id":712,"category":"Generics","question":"How do I use BiPredicate and BiAssessor to test two values?","url":"https://ek9.io/qa/QA0712.html","alternatePhrasings":["What is the difference between BiPredicate and BiAssessor?","How do I create a BiPredicate of (String, String)?","When should I use BiAssessor instead of BiPredicate?"],"answer":"BiPredicate and BiAssessor are the Boolean-returning two-parameter function types. They test a condition involving values 't' and 'u'.\n\nBIPREDICATE IS PURE\nBiPredicate of (T, U) takes parameters 't' and 'u' and returns 'r' as Boolean. Being pure, the test is stateless:\n  check <- () is BiPredicate of (String, Integer) as pure function\n    r: length t > u\n\nBIASSESSOR IS IMPURE\nBiAssessor of (T, U) has the same signature but is NOT pure:\n  logCheck <- () is BiAssessor of (String, Integer) as function\n    stdout <- Stdout()\n    stdout.println(`Assessing: ${t} vs ${u}`)\n    r: length t > u\n\nUSE CASES\nBiPredicate: Pure comparison, matching, or relationship testing.\nBiAssessor: Testing with logging, metrics, or state-dependent checks.\n\nSee Q54 for pure vs impure concepts. See Q709 for single-parameter Predicate/Assessor.","ek9Example":"defines module qa.genericsdeep.bipredicatebiassessor\n\n  defines function\n\n    <?-\n      Helper that applies a pure BiPredicate.\n    -?>\n    testPair() as pure\n      ->\n        text as String\n        threshold as Integer\n        check as BiPredicate of (String, Integer)\n      <- result as Boolean: check(text, threshold)\n\n    <?-\n      Helper that applies an impure BiAssessor.\n    -?>\n    assessPair()\n      ->\n        text as String\n        threshold as Integer\n        check as BiAssessor of (String, Integer)\n      <- result as Boolean: check(text, threshold)\n\n  defines program\n\n    BiPredicateBiAssessorDemo()\n      stdout <- Stdout()\n\n      //BiPredicate: pure, stateless test of two values\n      //Body uses 't' and 'u' for inputs, 'r' for return\n      lengthCheck <- () is BiPredicate of (String, Integer) as pure function\n        r: length t > u\n\n      stdout.println(`'Hello' longer than 3: ${testPair(\"Hello\", 3, lengthCheck)}`)\n      stdout.println(`'Hi' longer than 3: ${testPair(\"Hi\", 3, lengthCheck)}`)\n\n      //BiAssessor: impure, can log during assessment\n      logCheck <- () is BiAssessor of (String, Integer) as function\n        stdout <- Stdout()\n        stdout.println(`Assessing: length of '${t}' vs ${u}`)\n        r: length t > u\n\n      result <- assessPair(\"Testing\", 3, logCheck)\n      stdout.println(`BiAssessor result: ${result}`)","migrationContext":"Java: java.util.function.BiPredicate<T,U> — no purity distinction, supports and/or/negate. Kotlin: (T, U) -> Boolean — no pure/impure variants. Rust: Fn(&T, &U) -> bool vs FnMut(&T, &U) -> bool — structural. Go: func(T, U) bool — no purity. C#: Func<T1, T2, bool> — no purity concept. EK9: BiPredicate (pure) vs BiAssessor (impure) — compile-time enforced.","keywords":["biassessor","bipredicate","boolean","comparison","function-type","generic","impure","pure","side-effect","test","two-parameter"],"primaryTopics":[],"typicalErrors":[{"error":"E05150","correct":"() is BiPredicate of (String, Integer) as pure function","incorrect":"() is BiPredicate of (String, Integer) as function","explanation":"BiPredicate is pure — dynamic functions extending BiPredicate must use 'as pure function', not just 'as function'. The compiler requires the purity of the implementation to match its pure super type. See ek9 -h E05150 for details."}],"companions":[]}
{"id":713,"category":"Generics","question":"How do I use BiFunction and BiRoutine for two-input transformations?","url":"https://ek9.io/qa/QA0713.html","alternatePhrasings":["What is the difference between BiFunction and BiRoutine?","How do I create a BiFunction of (String, Integer, Boolean)?","When should I use BiRoutine instead of BiFunction?"],"answer":"BiFunction and BiRoutine are the three-type-parameter transformation types. They take inputs 't' of type T and 'u' of type U, and return 'r' of type R.\n\nBIFUNCTION IS PURE\nBiFunction of (T, U, R) takes two parameters and returns a result. Being pure, the transformation is deterministic:\n  combiner <- () is BiFunction of (String, Integer, String) as pure function\n    r: `${t}: ${u}`\n\nBIROUTINE IS IMPURE\nBiRoutine of (T, U, R) has the same signature but is NOT pure:\n  logCombiner <- () is BiRoutine of (String, Integer, String) as function\n    stdout <- Stdout()\n    stdout.println(`Combining: ${t} with ${u}`)\n    r: `${t}-${u}`\n\nCOMBINER PATTERN\nBiFunction is ideal for combining two values into one:\n  merge() as pure\n    -> a as String, b as Integer, combiner as BiFunction of (String, Integer, String)\n    <- result as String: combiner(a, b)\n\nSee Q54 for pure vs impure concepts. See Q710 for single-input Function/Routine.","ek9Example":"defines module qa.genericsdeep.bifunctionbiroutine\n\n  defines function\n\n    <?-\n      Helper that applies a pure BiFunction combiner.\n    -?>\n    merge() as pure\n      ->\n        name as String\n        count as Integer\n        combiner as BiFunction of (String, Integer, String)\n      <- result as String: combiner(name, count)\n\n    <?-\n      Helper that applies an impure BiRoutine combiner.\n    -?>\n    mergeWithEffects()\n      ->\n        name as String\n        count as Integer\n        combiner as BiRoutine of (String, Integer, String)\n      <- result as String: combiner(name, count)\n\n  defines program\n\n    BiFunctionBiRoutineDemo()\n      stdout <- Stdout()\n\n      //BiFunction: pure, deterministic two-input transform\n      //Body uses 't' and 'u' for inputs, 'r' for return\n      combiner <- () is BiFunction of (String, Integer, String) as pure function\n        r: `${t}: ${u}`\n\n      stdout.println(`Combined: ${merge(\"Alice\", 42, combiner)}`)\n\n      //BiFunction with Boolean result\n      checker <- () is BiFunction of (String, Integer, Boolean) as pure function\n        r: length t > u\n\n      stdout.println(`Length check: ${checker(\"Hello\", 3)}`)\n\n      //BiRoutine: impure, can log during transformation\n      logCombiner <- () is BiRoutine of (String, Integer, String) as function\n        stdout <- Stdout()\n        stdout.println(`Combining: ${t} with ${u}`)\n        r: `${t}-${u}`\n\n      result <- mergeWithEffects(\"Bob\", 99, logCombiner)\n      stdout.println(`BiRoutine result: ${result}`)","migrationContext":"Java: java.util.function.BiFunction<T,U,R> — no purity distinction, supports andThen composition. Kotlin: (T, U) -> R — no pure/impure variants. Rust: Fn(T, U) -> R (pure) vs FnMut(T, U) -> R (stateful). Go: func(T, U) R — no purity. C#: Func<T1, T2, TResult> — no purity concept. EK9: BiFunction (pure, deterministic) vs BiRoutine (impure, can access state) — compile-time enforced.","keywords":["bifunction","biroutine","combine","function-type","generic","impure","pure","side-effect","three-parameter","transform","two-input"],"primaryTopics":[],"typicalErrors":[{"error":"E05150","correct":"() is BiFunction of (String, Integer, String) as pure function","incorrect":"() is BiFunction of (String, Integer, String) as function","explanation":"BiFunction is pure — dynamic functions extending BiFunction must use 'as pure function', not just 'as function'. The compiler requires the purity of the implementation to match its pure super type. See ek9 -h E05150 for details."}],"companions":[]}
{"id":714,"category":"Generics","question":"How do I use the Comparator function type for ordering?","url":"https://ek9.io/qa/QA0714.html","alternatePhrasings":["How do I create a Comparator of String for custom sorting?","What is the Comparator function type signature in EK9?","How do I implement a custom comparison with Comparator?"],"answer":"Comparator of T is a pure function type that takes two T values 't1' and 't2' and returns 'r' as Integer.\n\nCOMPARATOR SIGNATURE\nComparator of T takes two same-type parameters and returns their ordering:\n  cmp <- () is Comparator of String as pure function\n    r: t1 <=> t2\nReturns negative if t1 < t2, zero if equal, positive if t1 > t2.\n\nALWAYS PURE\nComparator is always pure — there is no impure variant. Ordering must be deterministic.\n\nCUSTOM ORDERING\nReverse ordering by swapping t1 and t2:\n  reverseCmp <- () is Comparator of Integer as pure function\n    r: t2 <=> t1\nCompare by a derived property:\n  lengthCmp <- () is Comparator of String as pure function\n    r: length t1 <=> length t2\n\nTHE <=> OPERATOR\nEK9's comparison operator returns Integer: negative, zero, or positive.\n\nSee Q54 for pure function concepts. See Q89 for stream sort operations.","ek9Example":"defines module qa.genericsdeep.comparator\n\n  defines function\n\n    <?-\n      Helper that finds the smaller of two values using a Comparator.\n    -?>\n    minimum() as pure\n      ->\n        first as String\n        second as String\n        cmp as Comparator of String\n      <- result as String: first\n      if cmp(first, second) > 0\n        result: second\n\n  defines program\n\n    ComparatorDemo()\n      stdout <- Stdout()\n\n      fruitA <- \"apple\"\n      fruitB <- \"banana\"\n\n      //Natural String ordering — uses 't1' and 't2' from Comparator signature\n      natural <- () is Comparator of String as pure function\n        r: t1 <=> t2\n\n      stdout.println(`'${fruitA}' vs '${fruitB}': ${natural(fruitA, fruitB)}`)\n\n      //Reverse ordering — swap t1 and t2\n      reverse <- () is Comparator of String as pure function\n        r: t2 <=> t1\n\n      stdout.println(`'${fruitA}' vs '${fruitB}' reversed: ${reverse(fruitA, fruitB)}`)\n\n      //Compare by string length\n      byLength <- () is Comparator of String as pure function\n        r: length t1 <=> length t2\n\n      stdout.println(`'hi' vs 'hello' by length: ${byLength(\"hi\", \"hello\")}`)\n\n      //Minimum via Comparator delegate\n      smaller <- minimum(\"zebra\", fruitA, natural)\n      stdout.println(`Minimum: ${smaller}`)\n\n      //Integer Comparator\n      intCmp <- () is Comparator of Integer as pure function\n        r: t1 <=> t2\n\n      stdout.println(`42 vs 99: ${intCmp(42, 99)}`)","migrationContext":"Java: java.util.Comparator<T> — not a @FunctionalInterface by design (has default methods), supports reversed(), thenComparing() composition, can have side effects. Kotlin: Comparator<T> from Java stdlib, or compareBy/compareByDescending helpers. Rust: Fn(&T, &T) -> Ordering — structural, uses Ordering enum not Integer. Go: func(T, T) int — by convention, or sort.Interface for types. C#: Comparer<T> or Comparison<T> delegate. EK9: Comparator of T is always pure — deterministic ordering guaranteed by compiler, takes (T, T) returns Integer, no impure variant exists.","keywords":["comparator","comparison","delegate","function-type","generic","integer","ordering","pure","sort","spaceship"],"primaryTopics":["comparator","custom sort","comparator function"],"typicalErrors":[{"error":"E05150","correct":"() is Comparator of String as pure function","incorrect":"() is Comparator of String as function","explanation":"Comparator is always pure — dynamic functions extending Comparator must use 'as pure function', not just 'as function'. There is no impure variant of Comparator. See ek9 -h E05150 for details."}],"companions":[]}
{"id":715,"category":"Generics","question":"How do I use UnaryOperator for same-type transformations?","url":"https://ek9.io/qa/QA0715.html","alternatePhrasings":["What is UnaryOperator of T and how does it differ from Function?","How do I create a UnaryOperator of String?","When should I use UnaryOperator instead of Function?"],"answer":"UnaryOperator of T is a pure function type that takes 't' of type T and returns 'r' of the SAME type T.\n\nUNARYOPERATOR SIGNATURE\nUnaryOperator of T takes one parameter and returns the same type:\n  -> t as T\n  <- r as T?\nThe input type T and return type T are always identical.\n\nALWAYS PURE\nUnaryOperator is always pure — there is no impure variant.\n\nVS FUNCTION\nFunction of (T, R) allows different input and output types. UnaryOperator constrains both to T:\n  Function of (String, Integer): String -> Integer (different types)\n  UnaryOperator of String: String -> String (same type)\n\nUSING AS PARAMETER\nPass UnaryOperator as a parameter to constrain same-type transforms:\n  applyOp() as pure\n    -> item as String, op as UnaryOperator of String\n    <- result as String: op(item)\n\nUSE CASES\n- String transformations (trim, case conversion)\n- Numeric adjustments (doubling, negation)\n- Same-type transforms in stream map operations\n\nSee Q54 for pure function concepts. See Q710 for Function/Routine (different types). See Q89 for stream map operations.","ek9Example":"defines module qa.genericsdeep.unaryoperator\n\n  defines function\n\n    <?-\n      A user-defined abstract function with same-type signature.\n      Mirrors UnaryOperator's pattern but user-defined for demonstration.\n    -?>\n    stringTransform() as pure abstract\n      -> item as String\n      <- result as String?\n\n    <?-\n      Named implementation: converts to uppercase.\n    -?>\n    toUpper() is stringTransform as pure\n      -> item as String\n      <- result as String: item.upperCase()\n\n    <?-\n      Named implementation: trims whitespace.\n    -?>\n    toTrimmed() is stringTransform as pure\n      -> item as String\n      <- result as String: item.trim()\n\n    <?-\n      Helper that accepts a UnaryOperator as parameter.\n      Pure context — UnaryOperator is always pure.\n    -?>\n    applyOp() as pure\n      ->\n        item as String\n        op as UnaryOperator of String\n      <- result as String: op(item)\n\n    <?-\n      Helper that accepts the user-defined stringTransform.\n    -?>\n    applyTransform() as pure\n      ->\n        item as String\n        op as stringTransform\n      <- result as String: op(item)\n\n  defines program\n\n    UnaryOperatorDemo()\n      stdout <- Stdout()\n\n      //User-defined functions mirroring UnaryOperator pattern\n      stdout.println(`Upper: ${applyTransform(\"hello world\", toUpper)}`)\n      stdout.println(`Trimmed: '${applyTransform(\"  spaced  \", toTrimmed)}'`)\n\n      //Chained application\n      result <- applyTransform(applyTransform(\"  hello  \", toTrimmed), toUpper)\n      stdout.println(`Trimmed and uppercased: ${result}`)","migrationContext":"Java: java.util.function.UnaryOperator<T> extends Function<T,T> — no purity distinction, can have side effects. Kotlin: (T) -> T — no named type, no purity. Rust: Fn(T) -> T — structural, no named type. Go: func(T) T — no purity. C#: Func<T, T> — no named type, no purity. EK9: UnaryOperator is always pure, constrains input=output type, separate from Function — compile-time enforced.","keywords":["function-type","generic","identity","map","pure","same-type","stream","string","transform","unaryoperator"],"primaryTopics":[],"typicalErrors":[{"error":"E05150","correct":"toUpper() is stringTransform as pure\n      -> item as String\n      <- result as String: item.upperCase()","incorrect":"toUpper() is stringTransform\n      -> item as String\n      <- result as String: item.upperCase()","explanation":"When extending a pure abstract function (like a user-defined UnaryOperator pattern), the implementation must be marked 'as pure'. The compiler requires purity to match the super function. See ek9 -h E05150 for details."}],"companions":[]}
{"id":716,"category":"Advanced Type System","question":"How do arithmetic operators work on constrained types?","url":"https://ek9.io/qa/QA0716.html","alternatePhrasings":["Can I do math with constrained Integer types?","What happens when I add two constrained type values?","Do constrained types support increment and compound assignment?"],"answer":"Constrained types inherit arithmetic operators from their base type, with results returned as the constrained type.\n\nARITHMETIC RETURNS THE CONSTRAINED TYPE\nWhen you add two DrivingAge values, the result is DrivingAge:\n  sum <- age1 + age2\nThe compiler generates dual-signature operators: DrivingAge + DrivingAge and DrivingAge + Integer both work.\n\nDUAL SIGNATURES\nEvery arithmetic operator gets two forms:\n  result1 <- age1 + age2      //DrivingAge + DrivingAge -> DrivingAge\n  result2 <- age1 + 5          //DrivingAge + Integer -> DrivingAge\nThis lets you work with constrained types naturally without wrapping literals.\n\nMUTATING OPERATORS\nCompound assignment and increment delegate to the base type:\n  age1 += 5        //DrivingAge += Integer\n  age1 -= age2     //DrivingAge -= DrivingAge\n  age1++           //increment\n  age1--           //decrement\n\nMOD AND REM RETURN BASE TYPE\nThe mod and rem operators return the base type (Integer), not the constrained type:\n  modResult <- age1 mod 7    //returns Integer, not DrivingAge\n  remResult <- age1 rem 7    //returns Integer, not DrivingAge\n\nONLY BASE TYPE OPERATORS\nA constrained type can only use operators defined on its base type. If you constrain a record that has no + operator, you cannot add constrained values.\n\nSee Q257 for constrained type overview. See Q717 for comparison operators. See Q720 for which types can be constrained.","ek9Example":"defines module qa.advancedtypes.constrainedarithmetic\n\n  defines type\n\n    DrivingAge as Integer constrain as\n      >= 16 and <= 100\n\n  defines program\n\n    ConstrainedArithmeticDemo()\n      stdout <- Stdout()\n\n      // === ARITHMETIC: CT + CT -> CT ===\n      age1 <- DrivingAge(25)\n      age2 <- DrivingAge(30)\n\n      sum <- age1 + age2\n      stdout.println(`25 + 30 = ${sum}`)\n\n      diff <- age2 - age1\n      stdout.println(`30 - 25 = ${diff}`)\n\n      // === DUAL SIGNATURES: CT + BaseType -> CT ===\n      incremented <- age1 + 5\n      stdout.println(`25 + 5 = ${incremented}`)\n\n      decremented <- age2 - 10\n      stdout.println(`30 - 10 = ${decremented}`)\n\n      product <- age1 * 2\n      stdout.println(`25 * 2 = ${product}`)\n\n      // === MOD AND REM RETURN BASE TYPE (Integer) ===\n      modResult <- age1 mod 7\n      stdout.println(`25 mod 7 = ${modResult}`)\n\n      remResult <- age1 rem 7\n      stdout.println(`25 rem 7 = ${remResult}`)\n\n      // === MUTATING OPERATORS ===\n      mutable <- DrivingAge(20)\n\n      mutable += 5\n      stdout.println(`20 += 5 = ${mutable}`)\n\n      mutable -= 3\n      stdout.println(`25 -= 3 = ${mutable}`)\n\n      tooYoungValue <- 10\n      mutable += DrivingAge(tooYoungValue)\n      stdout.println(`22 += 10 = ${mutable}`)\n\n      // === INCREMENT AND DECREMENT ===\n      counter <- DrivingAge(40)\n\n      counter++\n      stdout.println(`40++ = ${counter}`)\n\n      counter--\n      stdout.println(`41-- = ${counter}`)","migrationContext":"Java: no constrained types, arithmetic on wrapper types requires unboxing. Python: no type-level constraints, arithmetic on custom classes via __add__. Rust: newtype pattern requires explicit Deref or operator trait implementations. Go: type aliases share operators but no value constraints. Kotlin: value classes wrap a single value but require manual operator definitions. EK9: constrained types automatically inherit all arithmetic operators from the base type with dual signatures (CT+CT and CT+BaseType), mutating operators delegate to base, mod/rem return base type.","keywords":["add","arithmetic","compound","constrained","increment","integer","mutating","operator","range","subtract"],"primaryTopics":[],"typicalErrors":[{"error":"E50060","correct":"sum <- age1 + age2","incorrect":"sum <- age1.intValue() + age2","explanation":"DrivingAge has no intValue() method in EK9. Constrained types do not have extra methods beyond those inherited from the base type. See ek9 -h E50060 for details."},{"error":"E50060","correct":"incremented <- age1 + 5","incorrect":"incremented <- age1.intValue() + 5","explanation":"DrivingAge has no intValue() method in EK9. Use the promote operator (#^) to extract the base type value. See ek9 -h E50060 for details."}],"companions":[]}
{"id":717,"category":"Advanced Type System","question":"How do comparison operators work between constrained types and their base types?","url":"https://ek9.io/qa/QA0717.html","alternatePhrasings":["Can I compare a constrained type with its base type value?","Why does DrivingAge < Integer work?","What comparison operators does a constrained type inherit?"],"answer":"Constrained types inherit comparison operators from the base type with dual signatures: each operator works with both the constrained type and the raw base type.\n\nDUAL SIGNATURE PATTERN\nEvery comparison operator gets two forms:\n  isEqual1 <- age1 == age2     //DrivingAge == DrivingAge -> Boolean\n  isEqual2 <- age1 == 25       //DrivingAge == Integer -> Boolean\nThis means you can compare constrained values directly with literals.\n\nWHY DUAL SIGNATURES MATTER\nWithout dual signatures, you would need to wrap every literal:\n  isEqual <- age1 == DrivingAge(25)    //works but verbose\n  isEqual <- age1 == 25                //also works — much cleaner\nThe compiler generates both forms automatically.\n\nALL COMPARISON OPERATORS\nThe full set of inherited comparisons (when base type has them):\n  ==     equality\n  <>     not equal\n  <      less than\n  <=     less or equal\n  >      greater than\n  >=     greater or equal\n  <=>    spaceship (returns Integer)\n\nOPERATORS FROM BASE TYPE ONLY\nIf the base type lacks an operator, the constrained type does not have it either. A record with only == and <> will not support <, <=, >, >= on its constrained type.\n\nCONTAINS AND MATCHES\nString-constrained types inherit contains and matches:\n  hasAlice <- name1 contains \"Alice\"    //String has contains\n  matched <- name1 matches /^[A-Z]/     //String has matches\nInteger-constrained types do NOT have these.\n\nSee Q257 for constrained type overview. See Q716 for arithmetic operators. See Q718 for the promote operator.","ek9Example":"defines module qa.advancedtypes.constrainedcomparisons\n\n  defines type\n\n    DrivingAge as Integer constrain as\n      >= 16 and <= 100\n\n    Name as String constrain as\n      matches /^[a-zA-Z -]+$/\n\n  defines program\n\n    ConstrainedComparisonDemo()\n      stdout <- Stdout()\n\n      // === EQUALITY: CT == CT and CT == BaseType ===\n      age1 <- DrivingAge(25)\n      age2 <- DrivingAge(30)\n\n      isEqual <- age1 == age2\n      stdout.println(`25 == 30: ${isEqual}`)\n\n      twentyFive <- 25\n      isEqualBase <- age1 == twentyFive\n      stdout.println(`25 == 25: ${isEqualBase}`)\n\n      // === NOT EQUAL ===\n      notEqual <- age1 <> age2\n      stdout.println(`25 <> 30: ${notEqual}`)\n\n      ninetyNine <- 99\n      notEqualBase <- age1 <> ninetyNine\n      stdout.println(`25 <> 99: ${notEqualBase}`)\n\n      // === ORDERING: CT < CT and CT < BaseType ===\n      isLess <- age1 < age2\n      stdout.println(`25 < 30: ${isLess}`)\n\n      fifty <- 50\n      isLessBase <- age1 < fifty\n      stdout.println(`25 < 50: ${isLessBase}`)\n\n      isGreater <- age2 > age1\n      stdout.println(`30 > 25: ${isGreater}`)\n\n      twenty <- 20\n      isGreaterBase <- age2 > twenty\n      stdout.println(`30 > 20: ${isGreaterBase}`)\n\n      // === SPACESHIP OPERATOR ===\n      cmpResult <- age1 <=> age2\n      stdout.println(`25 <=> 30: ${cmpResult}`)\n\n      // === STRING-CONSTRAINED: contains and matches ===\n      name1 <- Name(\"Alice\")\n      name2 <- Name(\"Bob\")\n\n      same <- name1 == name2\n      stdout.println(`Alice == Bob: ${same}`)\n\n      sameBase <- name1 == \"Alice\"\n      stdout.println(`Alice == 'Alice': ${sameBase}`)\n\n      matched <- name1 matches /^A/\n      stdout.println(`Alice matches ^A: ${matched}`)","migrationContext":"Java: no constrained types, comparisons use equals()/compareTo() methods. Python: no type constraints, comparison via __eq__/__lt__ etc. Rust: newtype pattern requires implementing PartialEq/PartialOrd traits manually. Go: type aliases inherit comparison operators but no value constraints. Kotlin: value classes require manual operator definitions. EK9: constrained types automatically inherit all comparison operators from the base type with dual signatures (CT==CT and CT==BaseType), enabling natural comparisons with both constrained values and raw literals.","keywords":["comparison","constrained","contains","dual","equals","inherit","matches","operator","signature"],"primaryTopics":[],"typicalErrors":[{"error":"E50060","correct":"isEqual <- age1 == age2","incorrect":"isEqual <- age1.equals(age2)","explanation":"DrivingAge has no equals() method in EK9. Use the == operator for equality comparison. See ek9 -h E50060 for details."},{"error":"E50060","correct":"sameBase <- name1 == \"Alice\"","incorrect":"sameBase <- name1.equals(\"Alice\")","explanation":"Name has no equals() method in EK9. Use the == operator for equality comparison. See ek9 -h E50060 for details."}],"companions":[]}
{"id":718,"category":"Advanced Type System","question":"How do I extract the base type value from a constrained type?","url":"https://ek9.io/qa/QA0718.html","alternatePhrasings":["What does the #^ promote operator do with constrained types?","How do I convert a DrivingAge back to an Integer?","Why can't I promote a constrained enumeration to its base enum?"],"answer":"The promote operator (#^) extracts the base type value from a constrained type, providing an explicit escape hatch for crossing type boundaries.\n\nPROMOTE RETURNS THE BASE TYPE\nThe #^ operator extracts the underlying value:\n  rawInt <- #^ age        //DrivingAge -> Integer\nThe result is the base type, not the constrained type. Only types whose base type defines promote (#^) support this operator.\n\nWHICH CONSTRAINED TYPES HAVE PROMOTE\nPromote is available when the base type defines it. Integer defines promote (to Float), so DrivingAge has #^. String does NOT define promote, so a String-constrained type like Name cannot use #^. Use $ (string conversion) instead for String-constrained types.\n\nCONSTRAINED ENUMS BLOCK PROMOTE\nConstrained enumerations deliberately prevent promotion back to the base enum:\n  redSuit <- RedCardSuit(\"Hearts\")\n  //Cannot do: cardSuit <- #^ redSuit   //No promote to CardSuit\nThis is by design: constrained enums are fully disconnected types. If you could promote RedCardSuit to CardSuit, it would bypass the type boundary.\n\nWHY PROMOTE EXISTS\nConstrained types are disconnected: DrivingAge is NOT an Integer subtype. You cannot pass DrivingAge where Integer is expected. Promote is the explicit conversion:\n  processInteger()\n    -> val as Integer\n    //...\n  age <- DrivingAge(25)\n  rawInt <- #^ age\n  processInteger(rawInt)     //extract then pass\n\nPROMOTE VS STRING\nTwo different conversions:\n  text <- $age          //String representation: \"25\"\n  raw <- #^ age         //Base type value: Integer 25\nBoth are useful but serve different purposes.\n\nSee Q257 for constrained type overview. See Q25 for promote operator basics. See Q722 for type hierarchy details.","ek9Example":"defines module qa.advancedtypes.constrainedpromote\n\n  defines type\n\n    DrivingAge as Integer constrain as\n      >= 16 and <= 100\n\n    Name as String constrain as\n      matches /^[a-zA-Z -]+$/\n\n  defines function\n\n    <?-\n      Accepts an Integer — cannot take DrivingAge directly.\n    -?>\n    processInteger() as pure\n      -> intValue as Integer\n      <- result as String: `Raw integer: ${intValue}`\n\n  defines program\n\n    ConstrainedPromoteDemo()\n      stdout <- Stdout()\n\n      age <- DrivingAge(25)\n\n      // === PROMOTE TO BASE TYPE ===\n      rawInt <- #^ age\n      stdout.println(`Promoted DrivingAge to Integer: ${rawInt}`)\n\n      // === PASS TO FUNCTION EXPECTING BASE TYPE ===\n      extracted <- #^ age\n      output <- processInteger(extracted)\n      stdout.println(output)\n\n      // === STRING CONVERSION VS PROMOTE ===\n      asText <- $age\n      stdout.println(`String conversion: ${asText}`)\n\n      asInteger <- #^ age\n      stdout.println(`Promote to Integer: ${asInteger}`)\n\n      // === STRING-CONSTRAINED: use $ not #^ ===\n      //String has no promote operator, so Name does not either.\n      //Use string conversion ($) instead:\n      name <- Name(\"Alice Smith\")\n      nameAsStr <- $name\n      stdout.println(`Name as String: ${nameAsStr}`)\n\n      // === ISSET STILL WORKS ===\n      isValid <- age?\n      stdout.println(`DrivingAge is set: ${isValid}`)\n\n      // === HASHCODE ===\n      hashVal <- #? age\n      stdout.println(`Hashcode: ${hashVal}`)","migrationContext":"Java: no constrained types, no promote concept. Explicit casting or wrapper.getValue() for custom wrappers. Python: no type constraints, no promote. Rust: newtype pattern uses .0 field access or Into trait for conversion. Go: type conversion syntax T(value) converts between compatible types. Kotlin: value classes use underlying property access. EK9: promote (#^) is the explicit operator for crossing constrained type boundaries — returns the base type value, available on value-constrained types but blocked on constrained enumerations.","keywords":["base-type","boundary","constrained","convert","disconnected","enum","explicit","extract","promote"],"primaryTopics":[],"typicalErrors":[{"error":"E50060","correct":"output <- processInteger(extracted)","incorrect":"output <- processInteger(extracted).toUpperCase()","explanation":"String has no toUpperCase() method in EK9. Use upperCase() instead. See ek9 -h E50060 for details."},{"error":"E50060","correct":"nameAsStr <- $name","incorrect":"nameAsStr <- name.getValue()","explanation":"Name has no getValue() method in EK9. Use the $ prefix operator to convert to String. See ek9 -h E50060 for details."}],"companions":[]}
{"id":719,"category":"Advanced Type System","question":"How do copy, replace, and merge operators work with constrained types?","url":"https://ek9.io/qa/QA0719.html","alternatePhrasings":["Can I use :=:, :^:, and :~: with constrained types?","How do I copy values between constrained type variables?","What is the difference between copy, replace, and merge for constrained types?"],"answer":"Constrained types inherit copy (:=:), replace (:^:), and merge (:~:) operators from their base type, with both operands being the constrained type.\n\nCOPY (:=:) DELEGATES TO BASE TYPE\nCopy duplicates the value from one variable to another:\n  target <- DrivingAge(50)\n  source <- DrivingAge(60)\n  target :=: source\n  //target is now 60\nBoth operands must be the same constrained type.\n\nREPLACE (:^:) OVERWRITES\nReplace destructively overwrites the target value:\n  target :^: DrivingAge(70)\n  //target is now 70\nThe semantics depend on the base type's replace implementation.\n\nMERGE (:~:) COMBINES\nMerge combines two values according to base type semantics. For Integer, this adds:\n  target :~: DrivingAge(80)\n  //for Integer: 70 + 80 = 150\nFor String, merge concatenates. For other types, merge follows that type's semantics.\n\nARGUMENT TYPE IS CONSTRAINED TYPE\nThese operators take the constrained type as argument, not the raw base type:\n  target :=: DrivingAge(50)    //correct\n  //target :=: 50              //wrong — cannot use raw Integer\n\nSee Q257 for constrained type overview. See Q241 for mutation operator details. See Q716 for arithmetic operators.","ek9Example":"defines module qa.advancedtypes.constrainedcopyreplacemerge\n\n  defines type\n\n    DrivingAge as Integer constrain as\n      >= 16 and <= 100\n\n  defines program\n\n    ConstrainedCopyReplaceMergeDemo()\n      stdout <- Stdout()\n\n      // === COPY (:=:) ===\n      target <- DrivingAge(50)\n      source <- DrivingAge(60)\n\n      stdout.println(`Before copy: target=${target}, source=${source}`)\n      target :=: source\n      stdout.println(`After copy: target=${target}`)\n\n      // === REPLACE (:^:) ===\n      target :^: DrivingAge(70)\n      stdout.println(`After replace with 70: target=${target}`)\n\n      // === MERGE (:~:) ===\n      //For Integer, merge adds the values\n      mergeTarget <- DrivingAge(20)\n      mergeSource <- DrivingAge(30)\n      mergeTarget :~: mergeSource\n      stdout.println(`After merge 20+30: mergeTarget=${mergeTarget}`)\n\n      // === COPY CONSTRUCTOR (separate from :=:) ===\n      original <- DrivingAge(25)\n      copied <- DrivingAge(original)\n      stdout.println(`Original: ${original}, Copied: ${copied}`)\n\n      // === ASSIGNMENT (:=) VS COPY (:=:) ===\n      a <- DrivingAge(40)\n      b <- DrivingAge(60)\n\n      //Assignment replaces the variable reference\n      a := b\n      stdout.println(`After assignment a := b: a=${a}`)\n\n      //Copy delegates to base type copy operator\n      c <- DrivingAge(40)\n      d <- DrivingAge(60)\n      c :=: d\n      stdout.println(`After copy c :=: d: c=${c}`)","migrationContext":"Java: no constrained types, no copy/replace/merge operators. Manual clone() or copy constructors. Python: copy.copy() and copy.deepcopy() for object copying. Rust: Clone trait for copy, no replace/merge concept. Go: assignment copies values for value types. Kotlin: data class copy() method. EK9: constrained types inherit :=: (copy), :^: (replace), :~: (merge) from their base type — these operate between same-type constrained values, delegating to the base type's implementation.","keywords":["assignment","constrained","copy","delegate","merge","mutating","operator","replace","transfer"],"primaryTopics":[],"typicalErrors":[{"error":"E50060","correct":"target <- DrivingAge(50)","incorrect":"target <- DrivingAge(50).intValue()","explanation":"DrivingAge has no intValue() method in EK9. DrivingAge is a constrained type, not a wrapper. See ek9 -h E50060 for details."},{"error":"E50060","correct":"copied <- DrivingAge(original)","incorrect":"copied <- DrivingAge(original).intValue()","explanation":"DrivingAge has no intValue() method in EK9. Use the promote operator (#^) to extract the Integer value. See ek9 -h E50060 for details."}],"companions":[]}
{"id":720,"category":"Advanced Type System","question":"What built-in types can be constrained in EK9?","url":"https://ek9.io/qa/QA0720.html","alternatePhrasings":["Which EK9 types support the constrain keyword?","Why can't I constrain a Boolean or JSON type?","What is error E04010 about?"],"answer":"EK9 allows constraining types that have comparison operators and meaningful value ranges. Not all types qualify.\n\nCONSTRAINABLE BUILT-IN TYPES\nThese types can be constrained with 'constrain as' or 'constrain':\n  String — constrain as matches /pattern/ or equality\n  Integer — constrain as > 0 and < 100\n  Float — constrain as > 0.0 and < 1.0\n  Date — constrain as >= 2024-01-01 and <= 2024-12-31\n  Time — constrain as >= 09:00 and <= 17:00\n  DateTime — constrain as >= date-time ranges\n  Duration — constrain as >= P1D and <= P30D\n  Millisecond — constrain as >= 0ms and <= 5000ms\n  Dimension — constrain as >= 0m and <= 100m\n  Money — constrain as >= 10000#GBP and <= 120000#GBP\n  Colour — constrain as #0099CC or #9900CC\n  Enumerations — constrain as \"Value1\" or \"Value2\"\n\nCONSTRAINABLE USER TYPES\nRecords and classes with comparison operators can be constrained:\n  ValidPerson as Person constrain as\n    matches /^[a-zA-Z]+ [a-zA-Z]+$/\nThe base type must have the operators used in the constraint expression.\n\nNON-CONSTRAINABLE TYPES (E04010)\nThese types CANNOT be constrained:\n  Boolean — already has only two values\n  JSON — dynamic structure, undefined comparison semantics\n  Functions — code references, not values\n  Traits — cannot be instantiated\n  Abstract classes — cannot be instantiated\n  Components — singleton services, not value types\n\nWHY THESE RESTRICTIONS\nConstraint expressions need comparison operators to evaluate values. Boolean is already maximally constrained. JSON, traits, and abstract classes cannot participate in meaningful value comparisons.\n\nSIMPLE ALIASING IS ALWAYS ALLOWED\nType aliasing without constraints works for any type:\n  Index as Integer          //alias, no constraint\n  Name as String            //alias, no constraint\n\nSee Q257 for constrained type overview. See Q722 for type hierarchy. See Q721 for constrained types as parameters.","ek9Example":"defines module qa.advancedtypes.constrainabletypes\n\n  defines type\n\n    // === STRING CONSTRAINT: regex pattern ===\n    Name as String constrain as\n      matches /^[a-zA-Z -]+$/\n\n    // === INTEGER CONSTRAINT: range ===\n    PositiveIndex as Integer constrain as\n      > 0 and < 1000\n\n    // === MONEY CONSTRAINT: salary range ===\n    Salary as Money constrain as\n      >= 10000#GBP and <= 120000#GBP\n\n    // === DATE CONSTRAINT: year range ===\n    RecentDate as Date constrain as\n      >= 2020-01-01 and <= 2030-12-31\n\n    // === COLOUR CONSTRAINT: specific values ===\n    BrandColour as Colour constrain as\n      #0099CC or #9900CC\n\n    // === SIMPLE ALIAS (no constraint) ===\n    Index as Integer\n\n  defines program\n\n    ConstrainableTypesDemo()\n      stdout <- Stdout()\n\n      // === STRING CONSTRAINT ===\n      // Use the fallible 'of' factory for untrusted input: it returns an UNSET value on a\n      // constraint failure rather than Panicking (a bare Name(\"123!@#\") would Panic at runtime,\n      // and is a compile error E08260 when the bad value is a literal constant).\n      if goodName <- Name().of(\"Alice Smith\")\n        stdout.println(`Valid name: ${goodName}`)\n\n      badName <- Name().of(\"123!@#\")\n      stdout.println(`Invalid name set: ${badName?}`)\n\n      // === INTEGER CONSTRAINT ===\n      if goodIndex <- PositiveIndex().of(42)\n        stdout.println(`Valid index: ${goodIndex}`)\n\n      badIndex <- PositiveIndex().of(0)\n      stdout.println(`Zero index set: ${badIndex?}`)\n\n      // === MONEY CONSTRAINT ===\n      if goodSalary <- Salary().of(50000#GBP)\n        stdout.println(`Valid salary: ${goodSalary}`)\n\n      badSalary <- Salary().of(5000#GBP)\n      stdout.println(`Low salary set: ${badSalary?}`)\n\n      // === DATE CONSTRAINT ===\n      if goodDate <- RecentDate().of(2024-06-15)\n        stdout.println(`Valid date: ${goodDate}`)\n\n      badDate <- RecentDate().of(2019-01-01)\n      stdout.println(`Old date set: ${badDate?}`)\n\n      // === COLOUR CONSTRAINT ===\n      if goodColour <- BrandColour().of(#0099CC)\n        stdout.println(`Valid colour: ${goodColour}`)\n\n      badColour <- BrandColour().of(#FF0000)\n      stdout.println(`Wrong colour set: ${badColour?}`)\n\n      // === SIMPLE ALIAS ALWAYS WORKS ===\n      idx <- Index(999)\n      stdout.println(`Alias index: ${idx}`)","migrationContext":"Java: no built-in constrained types. Bean Validation annotations (@Min, @Max, @Pattern) are runtime-only. Python: no type-level constraints, runtime validation with pydantic or dataclasses. Rust: no built-in constrained types, uses newtype pattern with constructor validation. Ada: subtype constraints on scalar types only (Integer, Float). Go: no type constraints, runtime validation. Kotlin: value classes with init blocks for runtime validation. EK9: built-in constraint syntax for any type with comparison operators — String (regex/equality), Integer/Float (ranges), Date/Time (ranges), Money (ranges), Colour (values), enumerations (value subsets), records/classes (operator expressions).","keywords":["E04010","boolean","built-in","constrain","constrainable","date","integer","money","string","types"],"primaryTopics":[],"typicalErrors":[{"error":"E50060","correct":"Name().of(\"Alice Smith\")","incorrect":"Name().of(\"Alice Smith\").getValue()","explanation":"Constrained types like Name have no getValue() wrapper method, so calling .getValue() fails to resolve. See ek9 -h E50060 for details."},{"error":"E50060","correct":"idx <- Index(999)","incorrect":"idx <- Index(999).intValue()","explanation":"Index has no intValue() method in EK9. Use the promote operator (#^) to extract the Integer value. See ek9 -h E50060 for details."}],"companions":[]}
{"id":721,"category":"Advanced Type System","question":"How do I use constrained types as function parameters for type safety?","url":"https://ek9.io/qa/QA0721.html","alternatePhrasings":["Why should I use constrained types in function signatures?","Can I pass a String where a Name is expected?","How do constrained types replace runtime validation?"],"answer":"Constrained types create type-safe function boundaries that eliminate an entire category of invalid-argument bugs at compile time.\n\nDISCONNECTED TYPES ENFORCE API CONTRACTS\nWhen a function takes a constrained type, you cannot pass the raw base type:\n  printName()\n    -> name as Name\n    //...\n  printName(\"Alice\")          //compile error: String is not Name\n  printName(Name(\"Alice\"))    //correct: construct the constrained type\n\nCONSTRUCTION IS THE VALIDATION BOUNDARY\nThe caller must construct the constrained type, which validates untrusted input via the fallible factory:\n  if validName <- Name().of(userInput)\n    printName(validName)       //guaranteed valid\nIf the input violates the constraint, of() yields an unset value and the guard's else branch handles it. A bare constructor instead ASSERTS validity — it Panics on a set out-of-range value (and a violating literal is the compile error E08260) — so use of() for untrusted input.\n\nGUARD AT THE BOUNDARY\nUse the guard pattern to validate at system entry points:\n  if age <- DrivingAge().of(rawInput)\n    processDriver(age)         //DrivingAge is validated\n  else\n    stdout.println(\"Invalid age\")\nInside processDriver, the DrivingAge is guaranteed to be in range.\n\nCONSTRAINED TYPES IN CLASS PROPERTIES\nClasses with constrained type fields enforce validity at construction:\n  Book\n    title as BookTitle?\n    author as AuthorName?\nEvery Book instance has a validated title and author.\n\nSee Q257 for constrained type overview. See Q269 for input validation patterns. See Q722 for type hierarchy.","ek9Example":"defines module qa.advancedtypes.constrainedparameters\n\n  defines type\n\n    Name as String constrain as\n      matches /^[a-zA-Z -]+$/\n\n    DrivingAge as Integer constrain as\n      >= 16 and <= 100\n\n    BookTitle as String constrain as\n      matches /^[a-zA-Z0-9 :'-]+$/\n\n  defines function\n\n    <?-\n      Takes a validated Name — cannot accept raw String.\n    -?>\n    formatGreeting() as pure\n      -> name as Name\n      <- greeting as String: `Hello, ${name}!`\n\n    <?-\n      Takes a validated DrivingAge — cannot accept raw Integer.\n    -?>\n    canRentCar() as pure\n      -> age as DrivingAge\n      <- allowed as Boolean?\n      rentalMinimum <- 21\n      allowed: age >= rentalMinimum\n\n  defines program\n\n    ConstrainedParametersDemo()\n      stdout <- Stdout()\n\n      // === MUST CONSTRUCT CONSTRAINED TYPE FIRST ===\n      validName <- Name(\"Alice Smith\")\n      greeting <- formatGreeting(validName)\n      stdout.println(greeting)\n\n      // === GUARD PATTERN AT BOUNDARY (fallible factory for untrusted input) ===\n      userInput <- \"Bob Jones\"\n\n      if checkedName <- Name().of(userInput)\n        result <- formatGreeting(checkedName)\n        stdout.println(result)\n      else\n        stdout.println(\"Invalid name provided\")\n\n      // === CONSTRAINED TYPE VALIDATES AT THE BOUNDARY ===\n      if driverAge <- DrivingAge().of(25)\n        canRent <- canRentCar(driverAge)\n        stdout.println(`Age 25, can rent: ${canRent}`)\n\n      // === INVALID INPUT IS REJECTED: of() RETURNS UNSET (a bare DrivingAge(10) would Panic) ===\n      invalidAge <- DrivingAge().of(10)\n      stdout.println(`Age 10 valid: ${invalidAge?}`)\n\n      // === CONSTRAINED FIELDS IN RECORDS ===\n      title <- BookTitle(\"The Great Adventure\")\n      stdout.println(`Book title: ${title}`)\n\n      // === MULTIPLE CONSTRAINED PARAMETERS ===\n      validTitle <- BookTitle(\"EK9 Guide\")\n      stdout.println(`Title valid: ${validTitle?}`)","migrationContext":"Java: parameter validation via Bean Validation annotations (@Valid, @NotNull), checked at runtime. Python: type hints are advisory, validation with pydantic at runtime. Rust: newtype pattern enforces boundaries but requires manual From/Into implementations. Go: no type constraints, runtime validation in function body. Kotlin: value classes provide some type safety but still subtypes. EK9: constrained types as parameters eliminate invalid-argument bugs at compile time — disconnected types prevent passing raw values, construction validates constraints, guard pattern handles boundary validation.","keywords":["api","boundary","constrained","construction","function","guard","parameter","type-safe","validate"],"primaryTopics":[],"typicalErrors":[{"error":"E50060","correct":"greeting <- formatGreeting(validName)","incorrect":"greeting <- formatGreeting(validName).toUpperCase()","explanation":"String has no toUpperCase() method in EK9. Use upperCase() instead. See ek9 -h E50060 for details."},{"error":"E50060","correct":"result <- formatGreeting(checkedName)","incorrect":"result <- formatGreeting(checkedName).toUpperCase()","explanation":"String has no toUpperCase() method in EK9. Use upperCase() instead. See ek9 -h E50060 for details."}],"companions":[]}
{"id":722,"category":"Advanced Type System","question":"How do constrained types relate to the type hierarchy?","url":"https://ek9.io/qa/QA0722.html","alternatePhrasings":["Why is a constrained type not a subtype of its base type?","What does LIKE-A mean for constrained types?","Can I constrain a constrained type?"],"answer":"Constrained types are deliberately disconnected from the type hierarchy. They share operators with their base type but are NOT subtypes.\n\nLIKE-A NOT IS-A\nDrivingAge shares Integer's operators but is not an Integer:\n  age <- DrivingAge(25)\n  //intVar as Integer: age    //compile error: INCOMPATIBLE_TYPES\n  intVar <- #^ age             //correct: use promote to cross boundary\nThis is a LIKE-A relationship: DrivingAge behaves like an Integer but is a separate type.\n\nSUPER IS ANY\nIn the type hierarchy, constrained types have Any as their super type, not the base type:\n  DrivingAge -> Any      (not DrivingAge -> Integer -> Any)\nThis prevents polymorphic substitution that would bypass validation.\n\nCANNOT CONSTRAIN A CONSTRAINED TYPE\nConstraining is a deliberately SIMPLE, one-level concept: a base type limited to a restricted range of values. You cannot constrain (or alias) an already-constrained type — once constrained it is its own distinct type and is no longer a candidate to be constrained. The compiler raises TYPE_CANNOT_BE_CONSTRAINED (E04010):\n  Index as Integer constrain as > 0\n  //DBIndex as Index constrain as < 1000000   //compile error E04010: 'Index' is not a candidate to be constrained\nTo make a more specific type, constrain the BASE type directly with the combined constraint:\n  BoundedIndex as Integer constrain as > 0 and < 1000000\n\nCANNOT EXTEND VIA INHERITANCE\nConstrained types are closed — you cannot extend them with 'extends' or 'as open', nor constrain/alias them further. The only way to create a more specific type is to declare a new constrained type over the BASE type with the combined constraint.\n\nEXPLICIT CONVERSION\nTo cross type boundaries, use constructors or promote:\n  age <- DrivingAge(25)\n  rawInt <- #^ age              //promote to Integer\n  newAge <- DrivingAge(rawInt)  //construct from Integer\n\nSee Q257 for constrained type overview. See Q718 for promote operator. See Q101 for closed-by-default types.","ek9Example":"defines module qa.advancedtypes.constrainedhierarchy\n\n  defines type\n\n    // === BASE CONSTRAINED TYPE ===\n    Index as Integer constrain as\n      > 0\n\n    // === A MORE SPECIFIC TYPE: constrain the BASE type directly (NOT 'as Index') ===\n    //You cannot constrain an already-constrained type — combine the constraints over the base type.\n    BoundedIndex as Integer constrain as\n      > 0 and < 1000000\n\n    DrivingAge as Integer constrain as\n      >= 16 and <= 100\n\n  defines function\n\n    <?-\n      Takes raw Integer — not DrivingAge.\n    -?>\n    processRawInteger() as pure\n      -> intValue as Integer\n      <- result as String: `Integer value: ${intValue}`\n\n  defines program\n\n    ConstrainedHierarchyDemo()\n      stdout <- Stdout()\n\n      // === LIKE-A NOT IS-A ===\n      age <- DrivingAge(25)\n      stdout.println(`DrivingAge: ${age}`)\n\n      //DrivingAge is NOT an Integer — must promote\n      rawInt <- #^ age\n      output <- processRawInteger(rawInt)\n      stdout.println(output)\n\n      // === CANNOT CONSTRAIN A CONSTRAINED TYPE: constrain the base directly ===\n      idx <- Index(42)\n      stdout.println(`Index: ${idx}`)\n\n      bIdx <- BoundedIndex(500)\n      stdout.println(`BoundedIndex: ${bIdx}`)\n\n      //Out-of-range via the fallible factory (never panics — returns an unset value)\n      badIdx <- BoundedIndex().of(0)\n      stdout.println(`BoundedIndex(0) valid: ${badIdx?}`)\n\n      // === PROMOTE WORKS ON ALL CONSTRAINED TYPES ===\n      rawFromIndex <- #^ idx\n      stdout.println(`Index promoted to Integer: ${rawFromIndex}`)\n\n      // === CONSTRUCTION CROSSES BOUNDARY ===\n      newAge <- DrivingAge(30)\n      rawAge <- #^ newAge\n      reconstructed <- DrivingAge(rawAge)\n      stdout.println(`Round-trip: ${reconstructed}`)\n\n      // === LIST OF CONSTRAINED TYPE WORKS ===\n      ages <- List() of DrivingAge\n      ages += DrivingAge(25)\n      ages += DrivingAge(30)\n      ages += DrivingAge(40)\n      stdout.println(`Ages count: ${length ages}`)","migrationContext":"Java: wrapper classes are subtypes of Object, custom value wrappers can be subtypes of base class. Python: no type hierarchy constraints, duck typing. Rust: newtype pattern creates a separate type (like EK9), explicit From/Into for conversions. Go: type definitions create new types disconnected from base (similar to EK9). Kotlin: value classes are still subtypes of their underlying type. Swift: no newtype pattern, typealias shares identity. EK9: constrained types are LIKE-A (share operators) not IS-A (not subtypes) — super is Any, fully disconnected, and constraining is strictly one-level: you constrain a base type, never another constrained type.","keywords":["E04010","alias","any","boundary","constrained","disconnected","explicit","extend","hierarchy","like-a","subtype"],"primaryTopics":[],"typicalErrors":[{"error":"E04010","correct":"BoundedIndex as Integer constrain as\n      > 0 and < 1000000","incorrect":"DBIndex as Index constrain as < 1000000","explanation":"You cannot constrain an already-constrained type (Index is itself constrained). Constraining is one-level — constrain the base type directly with the combined constraint. See ek9 -h E04010 for details."},{"error":"E50060","correct":"rawFromIndex <- #^ idx","incorrect":"rawFromIndex <- idx.intValue()","explanation":"Index has no intValue() method in EK9. Use the promote operator (#^) to extract the base type value. See ek9 -h E50060 for details."}],"companions":[]}
{"id":723,"category":"Syntax and Structure Rules","question":"Why does EK9 reject name$ instead of $name?","url":"https://ek9.io/qa/QA0723.html","alternatePhrasings":["Why must prefix operators come before the expression?","What is E01084 prefix operator wrong position?","How do I use the $ operator correctly in EK9?"],"answer":"EK9 has several PREFIX operators that must appear BEFORE the expression they operate on. Placing them after the expression triggers E01084.\n\nPREFIX OPERATORS\nThese operators extract values or transform expressions:\n- $   (string) - converts to string: $value\n- $$  (JSON) - converts to JSON: $$value\n- #?  (length/hashcode) - gets length or hashcode: #? collection\n- ~   (negate/reverse) - negates or reverses: ~condition\n- not (boolean negate) - negates boolean: not condition\n- abs (absolute) - absolute value: abs number\n- sqrt (square root) - square root: sqrt number\n- empty (empty check) - checks if empty: empty collection\n- length (length) - gets length: length collection\n\nCOMMON MISTAKE\nDevelopers from languages like Ruby or method-chaining styles write 'value$' or 'name#?'. In EK9 these are prefix operators so the correct form is '$value' and '#? name'.\n\nSee Q724 for suffix operator positioning. See Q238 for the complete operator set.","ek9Example":"defines module qa.syntaxrules.prefixoperator\n\n  defines function\n\n    formatValues() as pure\n      ->\n        name as String\n        score as Integer\n      <- result as String: String()\n\n      //Correct prefix operator usage\n      nameStr <- $name\n      scoreStr <- $score\n      nameLen <- length name\n      hash <- #? name\n      absScore <- abs score\n\n      result: `${nameStr} (${scoreStr}) len=${nameLen} hash=${hash} abs=${absScore}`\n\n  defines program\n\n    PrefixOperatorDemo()\n      stdout <- Stdout()\n\n      name <- \"Steve\"\n\n      //Correct: prefix operators before expression\n      stdout.println(`String: ${name}`)\n      stdout.println(`Length: ${length name}`)\n      stdout.println(`Hash: ${#? name}`)\n      stdout.println(`Abs: ${abs -42}`)\n      stdout.println(formatValues(\"Alice\", 95))","migrationContext":"Java: value.toString() is method call syntax. Ruby: array.first is postfix. Python: str(value) is function call. JavaScript: String(value) is constructor. EK9: $value is prefix operator, not method call or function.","keywords":["E01084","hashcode","length","negate","operator","position","prefix","string","syntax","wrong"],"primaryTopics":[],"typicalErrors":[{"error":"E50060","correct":"stdout.println(formatValues(\"Alice\", 95))","incorrect":"stdout.println(formatValues(\"Alice\", 95).toUpperCase())","explanation":"String has no toUpperCase() method in EK9. Use upperCase() instead. See ek9 -h E50060 for details."}],"companions":[]}
{"id":724,"category":"Syntax and Structure Rules","question":"Why does EK9 reject ?name instead of name?","url":"https://ek9.io/qa/QA0724.html","alternatePhrasings":["Why must the isSet operator come after the variable?","What is E01085 suffix operator wrong position?","How do I check if a value is set in EK9?"],"answer":"EK9's ? (isSet) operator is a SUFFIX operator that must appear AFTER the expression it checks. Placing it before the expression triggers E01085.\n\nSUFFIX OPERATOR\nThe main suffix operator in EK9:\n- ? (isSet) - checks if value is set/valid: value?\n\nCOMMON MISTAKE\nDevelopers from languages with prefix null checks (PHP isset(), Python is not None placed before) write '?value'. In EK9 the correct form is 'value?'.\n\nUSAGE PATTERNS\nThe ? operator is used extensively in EK9 for checking variable state:\n  if name?\n    stdout.println(name)\nThis checks whether name has been set to a meaningful value.\n\nGUARD EXPRESSIONS\nThe ? operator combines with guard assignments:\n  if record <- findRecord(key)\n    process(record)\nThe guard implicitly checks isSet on the assignment result.\n\nSee Q723 for prefix operator positioning. See Q22 for variable declarations and the tri-state model.","ek9Example":"defines module qa.syntaxrules.suffixoperator\n\n  defines function\n\n    describeValue() as pure\n      -> input as String\n      <- description as String: \"unset\"\n\n      //Correct: suffix ? after the variable\n      if input?\n        description: \"set to \" + input\n\n  defines program\n\n    SuffixOperatorDemo()\n      stdout <- Stdout()\n\n      name <- \"Steve\"\n      unsetStr <- String()\n\n      //Correct: ? operator after the expression\n      stdout.println(`Name set: ${name?}`)\n      stdout.println(`Unset set: ${unsetStr?}`)\n\n      stdout.println(describeValue(name))\n      stdout.println(describeValue(unsetStr))\n\n      //Guard expression (implicit isSet check)\n      first <- Optional(\"hello\")\n      if first?\n        if present <- first.get()\n          stdout.println(`Got: ${present}`)","migrationContext":"Java: value != null is comparison. PHP: isset($value) is prefix function. Python: value is not None is infix. Ruby: value.nil? is suffix method. Kotlin: value != null or value?.method for null safety. EK9: value? is suffix operator, checks tri-state isSet.","keywords":["E01085","check","isset","operator","position","set","suffix","syntax","tri-state","unset","wrong"],"primaryTopics":[],"typicalErrors":[{"error":"E50060","correct":"stdout.println(describeValue(name))","incorrect":"stdout.println(describeValue(name).toUpperCase())","explanation":"String has no toUpperCase() method in EK9. Use upperCase() instead. See ek9 -h E50060 for details."},{"error":"E50060","correct":"stdout.println(describeValue(unsetStr))","incorrect":"stdout.println(describeValue(unsetStr).toUpperCase())","explanation":"String has no toUpperCase() method in EK9. Use upperCase() instead. See ek9 -h E50060 for details."}],"companions":[]}
{"id":725,"category":"Syntax and Structure Rules","question":"Why must methods come before operators in an EK9 class?","url":"https://ek9.io/qa/QA0725.html","alternatePhrasings":["What is E01086 method after operator declaration?","What order do fields methods and operators go in EK9?","Why did my class fail with a method after default operator?"],"answer":"EK9 enforces strict ordering within class bodies. Fields come first, then methods, then operators. Once operator declarations begin, no more methods or fields can follow. Violating this triggers E01086.\n\nREQUIRED ORDER\n1. Fields (properties) first\n2. Constructors and methods second\n3. Operators third (including 'default operator')\n\nWHY ENFORCED\nStrict ordering makes classes consistently readable. Every EK9 class has the same structure: data at the top, behaviour in the middle, operators at the bottom. This eliminates the need to scan an entire class to find where operators or methods are declared.\n\nDEFAULT OPERATOR\nThe 'default operator' keyword generates standard operators (==, <>, <=>, $, #?, ?) based on fields. It must appear in the operators section, which is always last.\n\nSee Q93 for class definition basics. See Q96 for operator details. See Q238 for the fixed operator set.","ek9Example":"defines module qa.syntaxrules.classbodyorder\n\n  defines class\n\n    <?-\n      Correct ordering: fields, then methods, then operators.\n    -?>\n    Product\n      name <- String()\n      price <- Float()\n\n      Product()\n        ->\n          name as String\n          price as Float\n        this.name: name\n        this.price: price\n\n      describe()\n        <- rtn as String: `${name} at ${price}`\n\n      applyDiscount()\n        -> percent as Float\n        factor <- 1.0 - (percent / 100.0)\n        price: price * factor\n\n      //Operators must be last\n      default operator\n\n  defines program\n\n    ClassBodyOrderDemo()\n      stdout <- Stdout()\n\n      item <- Product(\"Widget\", 29.99)\n      stdout.println(item.describe())\n\n      item.applyDiscount(10.0)\n      stdout.println(item.describe())\n\n      same <- Product(\"Widget\", 29.99)\n      stdout.println(`Equal: ${item == same}`)","migrationContext":"Java: Members can appear in any order within a class. Python: Methods and special methods can be interspersed. Rust: impl blocks are separate from struct definition. Go: Methods are defined outside struct. Kotlin: Members can be in any order. EK9: Strict ordering enforced by grammar: fields then methods then operators.","keywords":["E01086","body","class","default","field","method","operator","ordering","structure","syntax"],"primaryTopics":[],"typicalErrors":[{"error":"E50060","correct":"stdout.println(item.describe())","incorrect":"stdout.println(item.describe().toUpperCase())","explanation":"String has no toUpperCase() method in EK9. Use upperCase() instead. See ek9 -h E50060 for details."}],"companions":[]}
{"id":726,"category":"Syntax and Structure Rules","question":"Why must references use the :: qualifier?","url":"https://ek9.io/qa/QA0726.html","alternatePhrasings":["What is E01010 invalid symbol by reference?","How do I import a type from another module in EK9?","What is the correct syntax for EK9 references?"],"answer":"EK9 uses the 'references' block to import symbols from other modules for short-name access. Each reference must use the fully qualified form: module.path::SymbolName. Using a bare name without the module qualifier triggers E01010.\n\nREFERENCES SYNTAX\nThe references block appears after the module declaration:\n  defines module my.app\n    references\n      net.customer.geometry::Circle\n      net.customer.geometry::Pi\n\nThe '::' separator divides the module path (left) from the symbol name (right).\n\nFULLY QUALIFIED INLINE\nYou can also use fully qualified names inline without a references block:\n  circle <- net.customer.geometry::Circle(5.0)\nThis works anywhere but is verbose for repeated use.\n\nAFTER IMPORTING\nOnce referenced, use the short name directly:\n  references\n    net.customer.geometry::Circle\n  defines program\n    Demo()\n      c <- Circle(5.0)\n\nALPHABETICAL ORDER\nReferences must be listed in alphabetical order (E11026).\n\nMULTI-FILE WORKSPACE\nThis Q&A demonstrates cross-module references: the referenced module (qa.syntaxrules.refsupport) is defined in a separate companion file, just as it would be in a real EK9 project.\n\nSee Q6 for module organization. See Q142 for cross-module constants.","ek9Example":"defines module qa.syntaxrules.modulereference\n\n  references\n    qa.syntaxrules.refsupport::Formatter\n    qa.syntaxrules.refsupport::MaxItems\n\n  defines program\n\n    ModuleReferenceDemo()\n      stdout <- Stdout()\n\n      //Short name access via references block\n      formatted <- Formatter(\"hello\")\n      stdout.println(formatted)\n      stdout.println(`Max items: ${MaxItems}`)\n\n      //Fully qualified inline access also works\n      formatted2 <- qa.syntaxrules.refsupport::Formatter(\"world\")\n      stdout.println(formatted2)","migrationContext":"Java: import com.example.MyClass with dot-separated path. Python: from module import Class. Go: import path/package. Rust: use crate::module::Type. C#: using Namespace.Type. EK9: references block with module.path::SymbolName using :: separator.","keywords":["E01010","access","cross","import","module","qualifier","reference","symbol","syntax"],"primaryTopics":[],"typicalErrors":[],"companions":[{"filename":"QA0726_referenced_module.ek9","source":"#!ek9\ndefines module qa.syntaxrules.refsupport\n\n  defines constant\n    MaxItems <- 50\n\n  defines function\n\n    Formatter() as pure\n      -> text as String\n      <- rtn as String: `[${text}]`\n\n//EOF\n","dev":false}]}
{"id":727,"category":"Syntax and Structure Rules","question":"What module names are reserved in EK9?","url":"https://ek9.io/qa/QA0727.html","alternatePhrasings":["What is E01020 invalid module name?","Why can I not use org.ek9 as my module name?","Which module namespaces are reserved in EK9?"],"answer":"The entire 'org.ek9.*' namespace is reserved for the EK9 standard library. User code cannot define modules in this namespace. Attempting to do so triggers E01020.\n\nRESERVED PREFIX\nAny module name starting with 'org.ek9.' is reserved:\n- org.ek9.lang   (core language types: String, Integer, List)\n- org.ek9.math   (mathematical types and functions)\n- org.ek9.* (any other org.ek9 sub-module)\n\nVALID MODULE NAMES\nUse your own namespace:\n- com.mycompany.myproject\n- net.example.utils\n- my.application\n- any.name.not.starting.with.org.ek9\n\nWHY RESERVED\nThe org.ek9 namespace contains the standard library types that the compiler relies on. Allowing user code in this namespace would risk name collisions with built-in types and could break compiler assumptions.\n\nSee Q6 for module organization. See Q13 for packages vs modules.","ek9Example":"defines module qa.syntaxrules.reservednames\n\n  defines function\n\n    greetUser() as pure\n      -> name as String\n      <- rtn as String: \"Hello, \" + name\n\n  defines program\n\n    ReservedNamesDemo()\n      stdout <- Stdout()\n\n      //This module uses a valid namespace\n      stdout.println(greetUser(\"Steve\"))\n      stdout.println(\"Module qa.syntaxrules.reservednames is valid\")\n      stdout.println(\"Module org.ek9.anything would be rejected\")","migrationContext":"Java: java.* and javax.* packages reserved for JDK. Python: no reserved namespaces but conventions exist. Go: standard library packages in specific paths. Rust: std crate is reserved. C#: System namespace is reserved. EK9: org.ek9.* reserved for standard library, compiler error if violated.","keywords":["E01020","library","module","name","namespace","org.ek9","reserved","standard","syntax"],"primaryTopics":[],"typicalErrors":[{"error":"E50060","correct":"stdout.println(greetUser(\"Steve\"))","incorrect":"stdout.println(greetUser(\"Steve\").toUpperCase())","explanation":"String has no toUpperCase() method in EK9. Use upperCase() instead. See ek9 -h E50060 for details."}],"companions":[]}
{"id":728,"category":"Code Quality","question":"What is the maximum nesting depth allowed in EK9 and why?","url":"https://ek9.io/qa/QA0728.html","alternatePhrasings":["What triggers E11011 excessive nesting?","How deep can I nest if statements in EK9?","What is the nesting depth limit?","Why does EK9 limit control flow nesting?"],"answer":"EK9 limits control flow nesting to 6 levels. Each nested if, while, for, switch, or try block increments the depth counter. Exceeding 6 triggers E11011.\n\nWHY 6 LEVELS\nResearch shows that code comprehension drops sharply beyond 3-4 levels of nesting. At 6 levels, a reader must track 6 simultaneous conditions to understand which branch executes. This is a maintenance and review burden that grows exponentially.\n\nTHIS IS A DESIGN LIMIT, NOT A TECHNICAL ONE\nThe compiler could handle 100 levels of nesting. The limit exists because deeply nested code is:\n1. Hard to review: reviewers miss edge cases in deeply nested branches\n2. Hard to test: each nesting level multiplies the number of test paths\n3. Hard to modify: adding a condition at level 5 requires understanding levels 1-4\n4. A code smell: deep nesting usually means the function is doing too much\n\nHOW TO STAY WITHIN LIMITS\n- Extract nested logic into helper functions (each function resets nesting)\n- Use guard expressions to flatten conditional chains\n- Use switch instead of chained if/else where appropriate\n- Apply early filtering with stream pipelines\n\nTHIS EXAMPLE\nThe processConfiguration function below uses exactly 6 levels of nesting. It compiles because 6 is the maximum allowed. Adding a 7th level inside the innermost block would trigger E11011.\n\nSee Q696 for complexity limits. See Q310 for code quality overview. See Q322 for quality enforcement.","ek9Example":"defines module qa.codequality.nestingboundary\n\n  defines constant\n\n    NETWORK_SECTION <- \"network\"\n    STORAGE_SECTION <- \"storage\"\n    APP_CONFIG <- \"app\"\n    MIN_SETTING_LENGTH <- 3\n\n  defines function\n\n    <?-\n      Helper function to check if a setting is enabled.\n    -?>\n    isEnabled() as pure\n      -> setting as String\n      <- enabled as Boolean: length setting > 0\n\n    <?-\n      Helper function to check if a setting name is valid.\n    -?>\n    isValidSetting() as pure\n      -> settingName as String\n      <- isValid as Boolean: length settingName > 1\n\n    <?-\n      This function uses exactly 6 levels of nesting.\n      This is the maximum allowed by EK9 before E11011 triggers.\n      Level 1: if configName?\n      Level 2: if sectionName?\n      Level 3: switch on section\n      Level 4: if settingName?\n      Level 5: if isEnabled check\n      Level 6: if length check\n    -?>\n    processConfiguration()\n      ->\n        configName as String\n        sectionName as String\n        settingName as String\n      <- message as String: \"no action\"\n\n      //Level 1\n      if configName?\n        //Level 2\n        if sectionName?\n          //Level 3: switch counts as a nesting level\n          switch sectionName\n            case NETWORK_SECTION\n              //Level 4\n              if settingName?\n                activated <- isEnabled(settingName)\n                //Level 5\n                if activated\n                  //Level 6: this is the maximum depth\n                  if length settingName > MIN_SETTING_LENGTH\n                    message: \"configured\"\n            case STORAGE_SECTION\n              message: \"storage handled\"\n            default\n              message: \"unknown section\"\n\n  defines program\n\n    NestingDepthBoundaryDemo()\n      stdout <- Stdout()\n\n      stdout.println(processConfiguration(APP_CONFIG, NETWORK_SECTION, \"proxy\"))\n      stdout.println(processConfiguration(APP_CONFIG, NETWORK_SECTION, \"\"))\n      stdout.println(processConfiguration(APP_CONFIG, STORAGE_SECTION, \"cache\"))\n      stdout.println(processConfiguration(\"\", \"\", \"\"))","migrationContext":"Java: no nesting limit (checked by optional tools like Checkstyle). Python: no formal limit (PEP 8 discourages deep nesting). C++: no limit. Rust: no limit (Clippy may warn). Go: no limit (convention discourages). EK9: 6-level hard limit enforced at compile time.","keywords":["E11011","boundary","clean-code","complexity","depth","if","limit","metric","nesting","quality","switch","while"],"primaryTopics":[],"typicalErrors":[{"error":"E50001","correct":"stdout.println(processConfiguration(APP_CONFIG, NETWORK_SECTION, \"proxy\"))","incorrect":"stdout.println(processConfig(APP_CONFIG, NETWORK_SECTION, \"proxy\"))","explanation":"The function processConfig is not defined in this module. The correct function name is processConfiguration. See ek9 -h E50001 for details."}],"companions":[]}
{"id":729,"category":"Code Quality","question":"What is the maximum class inheritance depth in EK9?","url":"https://ek9.io/qa/QA0729.html","alternatePhrasings":["What triggers E11019 excessive inheritance depth?","How deep can class hierarchies be?","What is the DIT limit for classes?","What are the DIT thresholds for each EK9 construct type?"],"answer":"EK9 limits inheritance depth to prevent fragile base class problems. The limits vary by construct type:\n- Class: DIT 4 (max 5 classes in a chain)\n- Record: DIT 2\n- Trait: DIT 4\n- Component: DIT 4\n- Function: DIT 3\n\nDIT (Depth of Inheritance Tree) counts ancestor levels. A base class has DIT=0, its child DIT=1, and so on.\n\nWHY LIMIT INHERITANCE DEPTH\nThis is a DESIGN limit, not a technical one. Deep hierarchies cause:\n1. Fragile base class problem: changes at the top cascade unpredictably through all descendants\n2. Comprehension burden: understanding a class at DIT=6 requires reading 7 class definitions\n3. Constructor complexity: super() chains grow error-prone with each level\n4. Testing explosion: each level multiplies the number of state combinations to test\n\nEK9 OFFERS BETTER ALTERNATIVES\nInstead of deep inheritance, EK9 provides:\n- Traits for shared behavior without hierarchy depth\n- Composition (has-a) instead of inheritance (is-a)\n- The 'by' delegation keyword for forwarding calls\n\nTHIS EXAMPLE\nThe hierarchy below has DIT=4 for the leaf class (exactly at the class limit). Adding one more intermediate level would trigger E11019.\n\nSee Q697 for inheritance depth overview. See Q611 for hierarchy validation. See Q602 for circular hierarchy detection.","ek9Example":"defines module qa.codequality.inheritanceboundary\n\n  defines trait\n\n    <?-\n      Traits add behavior without increasing inheritance depth.\n    -?>\n    Describable\n      describe() as pure\n        <- rtn as String: \"describable\"\n\n  defines class\n\n    //DIT=0: abstract root\n    Vehicle as abstract\n      engineType() as pure abstract\n        <- rtn as String?\n\n      default operator ?\n\n    //DIT=1: first concrete level\n    TwoWheeler extends Vehicle as open\n      wheelCount <- 2\n\n      override engineType() as pure\n        <- rtn as String: \"two-wheeler\"\n\n      getWheelCount() as pure\n        <- rtn as Integer: wheelCount\n\n      default operator ?\n\n    //DIT=2: specialization\n    Motorcycle extends TwoWheeler as open\n      displacement <- Integer()\n\n      Motorcycle()\n        -> displacement as Integer\n        this.displacement: displacement\n\n      getDisplacement() as pure\n        <- rtn as Integer: displacement\n\n      override engineType() as pure\n        <- rtn as String: \"motorcycle\"\n\n      default operator ?\n\n    //DIT=3: further specialization\n    SportsBike extends Motorcycle as open\n      hasFairing <- Boolean()\n\n      SportsBike()\n        ->\n          displacement as Integer\n          hasFairing as Boolean\n        super(displacement)\n        this.hasFairing: hasFairing\n\n      override engineType() as pure\n        <- rtn as String: \"sports\"\n\n      default operator ?\n\n    //DIT=4: exactly at the class limit\n    //Adding one more level here would trigger E11019\n    RacingBike extends SportsBike\n      raceCategory <- String()\n\n      RacingBike()\n        ->\n          displacement as Integer\n          raceCategory as String\n        super(displacement: displacement, hasFairing: true)\n        this.raceCategory: raceCategory\n\n      getRaceCategory() as pure\n        <- rtn as String: raceCategory\n\n      override engineType() as pure\n        <- rtn as String: `racing (${raceCategory})`\n\n      default operator ?\n\n  defines program\n\n    InheritanceDepthBoundaryDemo()\n      stdout <- Stdout()\n\n      racer <- RacingBike(displacement: 600, raceCategory: \"Supersport\")\n      stdout.println(racer.engineType())\n      stdout.println(racer.getRaceCategory())\n      stdout.println($racer.getDisplacement())\n      stdout.println($racer.getWheelCount())","migrationContext":"Java: no depth limit (only convention). Python: MRO handles deep chains but they are a smell. C++: no depth limit. Kotlin: no depth limit. Rust: no inheritance. Go: no inheritance (uses embedding). EK9: compile-time depth limit with different thresholds per construct type.","keywords":["DIT","E11019","base","boundary","class","clean-code","composition","depth","fragile","hierarchy","inheritance","limit","quality","trait"],"primaryTopics":[],"typicalErrors":[{"error":"E50060","correct":"    RacingBike extends SportsBike","incorrect":"    TrackBike extends SportsBike as open\n      default operator ?\n\n    RacingBike extends TrackBike","explanation":"Adding another level to the inheritance chain pushes DIT from 4 to 5, exceeding the class limit of 4. Use traits or composition instead of deeper inheritance. See ek9 -h E50060 for details."}],"companions":[]}
{"id":730,"category":"Code Quality","question":"What is a data clump and when does EK9 detect it?","url":"https://ek9.io/qa/QA0730.html","alternatePhrasings":["What triggers E11053 data clump detected?","How many functions sharing parameters trigger a data clump?","What is the data clump threshold?","Why does EK9 enforce the data clump rule?"],"answer":"A data clump is a group of parameters that always appear together across multiple callables. EK9 detects data clumps when 3 or more functions share 4 or more matching parameters.\n\nTHRESHOLD\n- Minimum shared parameters: 4\n- Minimum callables sharing them: 3\n\nWHY DATA CLUMPS ARE A PROBLEM\nThis is a DESIGN problem, not a technical one:\n1. Maintenance burden: changing the group requires updating every callable that uses it\n2. Missing abstraction: parameters that always travel together are a type waiting to be extracted\n3. Inconsistency risk: one function might swap parameter order, causing subtle bugs\n4. Testing complexity: 4+ parameters create a combinatorial explosion of test cases\n\nTHE FIX\nExtract the repeated parameter group into a record:\n  // Before (data clump):\n  //   formatAddress(street, city, state, zip)\n  //   validateAddress(street, city, state, zip)\n  //   normalizeAddress(street, city, state, zip)\n  // After (extracted record):\n  //   formatAddress(address as Address)\n\nTHIS EXAMPLE\nThe code below has only 2 functions sharing 4 parameters. This is below the threshold of 3 callables. A third function using the same parameter group would trigger E11053.\n\nSee Q310 for code quality overview. See Q322 for quality enforcement. See Q696 for complexity limits.","ek9Example":"defines module qa.codequality.dataclumpboundary\n\n  defines function\n\n    <?-\n      First function using the address parameter group.\n      Having 2 functions with the same 4 parameters is allowed.\n    -?>\n    formatAddress() as pure\n      ->\n        streetLine as String\n        cityName as String\n        regionName as String\n        postalCode as String\n      <- formatted as String: `${streetLine}, ${cityName}, ${regionName} ${postalCode}`\n\n    <?-\n      Second function using the same address parameter group.\n      Still below the threshold of 3 callables.\n    -?>\n    validateAddress() as pure\n      ->\n        streetLine as String\n        cityName as String\n        regionName as String\n        postalCode as String\n      <- isValid as Boolean: false\n\n      if streetLine? and cityName? and regionName? and postalCode?\n        isValid: length streetLine > 0 and length postalCode > 0\n\n    <?-\n      Third function with DIFFERENT parameters.\n      This does NOT form a data clump because the parameters differ.\n    -?>\n    processOrder() as pure\n      -> orderDetail as String\n      <- processed as String: orderDetail\n\n  defines program\n\n    DataClumpBoundaryDemo()\n      stdout <- Stdout()\n\n      formatted <- formatAddress(streetLine: \"123 Main St\", cityName: \"Springfield\", regionName: \"IL\", postalCode: \"62701\")\n      stdout.println(formatted)\n\n      isValid <- validateAddress(streetLine: \"123 Main St\", cityName: \"Springfield\", regionName: \"IL\", postalCode: \"62701\")\n      stdout.println(`Valid: ${isValid}`)\n\n      processed <- processOrder(\"ORD-001\")\n      stdout.println(processed)","migrationContext":"Java: no built-in data clump detection (PMD and SonarQube can detect, optional). Python: no detection. C++: no detection. Rust: no detection (tuples and structs discourage clumps). Go: no detection. EK9: compile-time data clump detection, cannot be suppressed.","keywords":["E11053","Fowler","boundary","clean-code","clump","data","extract","limit","parameter","quality","record","refactoring","threshold"],"primaryTopics":[],"typicalErrors":[{"error":"E06270","correct":"    processOrder() as pure\n      -> orderDetail as String\n      <- processed as String: orderDetail","incorrect":"    processOrder() as pure\n      ->\n        streetLine as String\n        cityName as String\n        regionName as String\n        postalCode as String\n      <- processed as String: streetLine","explanation":"Adding a third function with the same 4 parameters (streetLine, cityName, regionName, postalCode) creates a data clump detected by E06270. Extract these parameters into an Address record. See ek9 -h E06270 for details."}],"companions":[]}
{"id":731,"category":"Code Quality","question":"What is the type traversal limit and why does EK9 enforce it?","url":"https://ek9.io/qa/QA0731.html","alternatePhrasings":["What triggers E11054 excessive type traversal?","How many type transitions are allowed in a method chain?","What is the Law of Demeter in EK9?","Why does EK9 limit dot-chain depth?"],"answer":"EK9 limits method chains to 3 type transitions. Each time a dot-chain crosses from one type to another, it counts as a transition. Exceeding 3 triggers E11054.\n\nIMPORTANT DISTINCTION\nSame-type method chains do NOT count as transitions:\n  name.trim().upperCase().lowerCase()  // 0 transitions (all String)\nOnly CROSS-TYPE chains count:\n  dept.getManager().getAddress().getCity()  // 3 transitions\n\nWHY 3 TRANSITIONS MAXIMUM\nThis enforces the Law of Demeter (principle of least knowledge):\n1. Structural coupling: long chains couple your code to the internal structure of distant objects\n2. Fragile chains: changing any intermediate type breaks all callers\n3. Testability: mocking a 4-deep chain requires 4 mock objects\n4. Encapsulation violation: reaching through objects exposes internal structure\n\nHOW TO FIX\nAdd a convenience method that encapsulates the traversal:\n  // Before: dept.getManager().getAddress().getRegion().getCity()\n  // After:  dept.getManagerCity()\n  // The Department class adds: getManagerCity() = getManager().getAddress().getRegion().getCity()\n\nTHIS EXAMPLE\nThe method chain below crosses exactly 3 type boundaries (Department to Manager to Address to String). The Address class also has a getRegion() method returning a Region object. Adding that extra traversal step to reach the city via region would push from 3 to 4 transitions, triggering E11054.\n\nSee Q310 for code quality overview. See Q696 for complexity limits. See Q322 for quality enforcement.","ek9Example":"defines module qa.codequality.typetraversalboundary\n\n  defines class\n\n    <?-\n      Geographic region containing a city name.\n    -?>\n    Region\n      regionCity <- String()\n\n      Region()\n        -> regionCity as String\n        this.regionCity: regionCity\n\n      getCity() as pure\n        <- rtn as String: regionCity\n\n      default operator ?\n\n    <?-\n      Address that belongs to a geographic region.\n      Has both getCity() for direct access and getRegion() for region traversal.\n    -?>\n    Address\n      cityLabel <- String()\n      addressRegion <- Region()\n\n      Address()\n        ->\n          cityLabel as String\n          addressRegion as Region\n        this.cityLabel: cityLabel\n        this.addressRegion: addressRegion\n\n      getCity() as pure\n        <- rtn as String: cityLabel\n\n      getRegion() as pure\n        <- rtn as Region: addressRegion\n\n      default operator ?\n\n    <?-\n      Manager type that holds an address.\n    -?>\n    Manager\n      managerName <- String()\n      homeAddress <- Address()\n\n      Manager()\n        ->\n          managerName as String\n          homeAddress as Address\n        this.managerName: managerName\n        this.homeAddress: homeAddress\n\n      getName() as pure\n        <- rtn as String: managerName\n\n      getAddress() as pure\n        <- rtn as Address: homeAddress\n\n      default operator ?\n\n    <?-\n      Department type that holds a manager.\n      Method chains from Department cross 3 type boundaries maximum.\n    -?>\n    Department\n      deptName <- String()\n      deptManager <- Manager()\n\n      Department()\n        ->\n          deptName as String\n          deptManager as Manager\n        this.deptName: deptName\n        this.deptManager: deptManager\n\n      getName() as pure\n        <- rtn as String: deptName\n\n      getManager() as pure\n        <- rtn as Manager: deptManager\n\n      default operator ?\n\n  defines program\n\n    TypeTraversalBoundaryDemo()\n      stdout <- Stdout()\n\n      addressRegion <- Region(\"Springfield\")\n      homeAddress <- Address(cityLabel: \"Springfield\", addressRegion: addressRegion)\n      deptManager <- Manager(managerName: \"Alice\", homeAddress: homeAddress)\n      department <- Department(deptName: \"Engineering\", deptManager: deptManager)\n\n      //3 type transitions: Department -> Manager -> Address -> String\n      //This is exactly at the boundary\n      managerCity <- department.getManager().getAddress().getCity()\n      stdout.println(managerCity)\n\n      //Same-type chains: 0 transitions (all String)\n      processed <- managerCity.trim().upperCase()\n      stdout.println(processed)\n\n      //2 transitions: Department -> Manager -> String (within limit)\n      managerLabel <- department.getManager().getName()\n      stdout.println(managerLabel)","migrationContext":"Java: no type traversal limit (Demeter checked by optional tools). Python: no limit. C++: no limit. Rust: method chains common, no cross-type limit. Go: no limit (short method chains by convention). EK9: 3-transition hard limit enforced at compile time.","keywords":["Demeter","E11054","boundary","chain","clean-code","coupling","dot","encapsulation","limit","quality","transition","traversal","type"],"primaryTopics":[],"typicalErrors":[{"error":"E11054","correct":"      managerCity <- department.getManager().getAddress().getCity()","incorrect":"      managerCity <- department.getManager().getAddress().getRegion().getCity()","explanation":"Adding an extra type transition (Address to Region to String instead of Address to String) pushes the chain from 3 to 4 type transitions, exceeding the limit. Encapsulate the traversal in a convenience method. See ek9 -h E11054 for details."}],"companions":[]}
{"id":732,"category":"Code Quality","question":"Why must every constructor, operator, and pure function return be captured?","url":"https://ek9.io/qa/QA0732.html","alternatePhrasings":["What triggers E11050 discarded operator return?","What triggers E11055 discarded constructor return?","What triggers E11051 discarded pure function return?","When can I ignore a function return value?"],"answer":"EK9 requires that return values from constructors, computational operators, and pure functions are always captured. There is no threshold: discarding any of these is always an error.\n\nTHIS IS A DESIGN RULE, NOT A THRESHOLD\nUnlike nesting depth or inheritance depth, this check has no numeric boundary. It is binary: either the return is captured or it is not.\n\nWHY EACH CATEGORY IS ENFORCED\n\nCONSTRUCTOR RETURNS (E11055)\nA constructor creates an object. Calling it as a bare statement means the object is immediately garbage collected. This is always either a bug (forgot the assignment) or a misuse.\n\nOPERATOR RETURNS (E11050)\nComputational operators (+, -, *, /) produce new values without modifying the original. Discarding the result means the computation was wasted work.\n\nPURE FUNCTION RETURNS (E11051)\nPure functions have no side effects. If the return is discarded, the entire call did nothing observable. This is dead code.\n\nRESULT/OPTIONAL RETURNS (E11052)\nResult and Optional types exist to force error handling. Discarding them defeats their purpose, like catching an exception and silently ignoring it.\n\nWHEN RETURNS CAN BE IGNORED\nImpure functions CAN be called for their side effects:\n  stdout.println(\"hello\")  // Impure: called for side effect, return (Void) is fine to ignore\n  list += item              // Mutation operator: modifies list in place\n\nSee Q316 for discarded returns overview. See Q693 for captured operator returns. See Q310 for code quality overview.","ek9Example":"defines module qa.codequality.discardedreturnboundary\n\n  defines function\n\n    <?-\n      Pure function: return value must always be captured.\n    -?>\n    sanitizeInput() as pure\n      -> rawInput as String\n      <- cleaned as String: rawInput.trim()\n\n    <?-\n      Pure function: validates input length.\n    -?>\n    checkLength() as pure\n      -> inputText as String\n      <- isValid as Boolean: length inputText > 0\n\n  defines class\n\n    <?-\n      Simple sensor class demonstrating constructor capture.\n    -?>\n    Sensor\n      reading <- Float()\n\n      Sensor()\n        -> reading as Float\n        this.reading: reading\n\n      getReading() as pure\n        <- rtn as Float: reading\n\n      default operator ?\n\n  defines program\n\n    DiscardedReturnBoundaryDemo()\n      stdout <- Stdout()\n\n      //Correct: constructor return captured\n      temperatureSensor <- Sensor(98.6)\n      stdout.println($temperatureSensor.getReading())\n\n      //Correct: pure function return captured\n      trimmedInput <- sanitizeInput(\"  hello  \")\n      stdout.println(trimmedInput)\n\n      //Correct: pure function return captured (independent variable)\n      isValid <- checkLength(\"test\")\n      stdout.println(`Valid: ${isValid}`)\n\n      //Impure: println called for side effect, OK to not capture\n      stdout.println(\"done\")","migrationContext":"Java: all return values silently discardable. Rust: #[must_use] attribute (opt-in, suppressible). Go: blank identifier _ explicitly discards. Python: no enforcement. C++: [[nodiscard]] attribute (opt-in). EK9: automatic enforcement for all constructors, pure functions, computational operators, and Result/Optional returns.","keywords":["E11050","E11051","E11052","E11055","boundary","capture","clean-code","code","constructor","dead","discard","operator","pure","quality","return"],"primaryTopics":[],"typicalErrors":[{"error":"E11055","correct":"      temperatureSensor <- Sensor(98.6)\n      stdout.println($temperatureSensor.getReading())","incorrect":"      Sensor(98.6)\n      stdout.println(\"98.6\")","explanation":"Constructor return discarded. The Sensor object is created then immediately garbage collected. Assign it to a variable. See ek9 -h E11055 for details."},{"error":"E11051","correct":"      trimmedInput <- sanitizeInput(\"  hello  \")\n      stdout.println(trimmedInput)","incorrect":"      sanitizeInput(\"  hello  \")\n      stdout.println(\"hello\")","explanation":"Pure function sanitizeInput has no side effects. Discarding its return value makes the call completely useless. See ek9 -h E11051 for details."}],"companions":[]}
{"id":733,"category":"Code Quality","question":"What is the combined complexity-size check and when does it trigger?","url":"https://ek9.io/qa/QA0733.html","alternatePhrasings":["What triggers E11020 combined complexity size?","How do complexity and size interact in EK9?","What is the combined complexity threshold?","Why does EK9 check both complexity and size together?"],"answer":"EK9 checks that functions are not both moderately complex AND moderately large at the same time. A function can be somewhat complex OR somewhat long, but not both.\n\nFORMULA\n(complexity / maxComplexity) x (statements / maxStatements) must be 0.50 or less\n\nFor functions: maxComplexity = 45, maxStatements = 150\nFor methods: maxComplexity = 45, maxStatements = 100\nFor operators: maxComplexity = 45, maxStatements = 50\n\nWHY THIS CHECK EXISTS\nIndividual complexity and statement count checks catch extreme cases. But a function can pass both individually while still being a maintenance problem:\n- 30 complexity (passes: under 45) + 120 statements (passes: under 150)\n- Combined: (30/45) x (120/150) = 0.67 x 0.80 = 0.53 (FAILS: over 0.50)\n\nTHIS IS A DESIGN INSIGHT\nA function that is 67% of max complexity AND 80% of max size is carrying too much responsibility. Neither metric alone triggers a violation, but together they reveal a function that needs decomposition.\n\nHOW TO FIX\nDecompose into smaller functions. Reducing EITHER complexity (fewer branches) OR size (fewer statements) brings the combined ratio under the threshold.\n\nTHIS EXAMPLE\nThe analyzeMetrics function below has moderate complexity and moderate size, but the combined ratio stays well under 0.50 because the logic is decomposed into helper functions. A function that triggers E11020 would need 30+ branches AND 100+ statements simultaneously, which is too large for a concise example but exactly the kind of monolithic function this check prevents.\n\nSee Q696 for complexity limits. See Q310 for code quality overview. See Q322 for quality enforcement.","ek9Example":"defines module qa.codequality.combinedcomplexityboundary\n\n  defines constant\n\n    THRESHOLD_LOW <- 25\n    THRESHOLD_MEDIUM <- 50\n    THRESHOLD_HIGH <- 75\n    THRESHOLD_EXCELLENT <- 90\n\n  defines function\n\n    <?-\n      Classifies a numeric score into a category.\n      Small, focused function: low complexity, few statements.\n    -?>\n    classifyLevel() as pure\n      -> scoreValue as Integer\n      <- scoreLevel as String: \"unknown\"\n\n      if scoreValue < THRESHOLD_LOW\n        scoreLevel: \"low\"\n      else if scoreValue < THRESHOLD_MEDIUM\n        scoreLevel: \"medium\"\n      else if scoreValue < THRESHOLD_HIGH\n        scoreLevel: \"high\"\n      else\n        scoreLevel: \"excellent\"\n\n    <?-\n      Generates a description from a classified level.\n      Separate function keeps both complexity and size low.\n    -?>\n    describeLevel() as pure\n      -> scoreLevel as String\n      <- classification as String: \"unclassified\"\n\n      if scoreLevel == \"low\"\n        classification: \"needs improvement\"\n      else if scoreLevel == \"medium\"\n        classification: \"average performer\"\n      else if scoreLevel == \"high\"\n        classification: \"strong performer\"\n      else if scoreLevel == \"excellent\"\n        classification: \"outstanding\"\n\n    <?-\n      Analyzes metrics by composing small focused functions.\n      Combined complexity-size stays well under 0.50 because\n      logic is decomposed into classifyLevel and describeLevel.\n    -?>\n    analyzeMetrics() as pure\n      -> scoreValue as Integer\n      <- report as String: \"\"\n\n      scoreLevel <- classifyLevel(scoreValue)\n      classification <- describeLevel(scoreLevel)\n      passed <- scoreValue >= THRESHOLD_MEDIUM\n\n      if passed\n        report: `${classification} (${scoreLevel}) PASS`\n      else\n        report: `${classification} (${scoreLevel}) FAIL`\n\n  defines program\n\n    CombinedComplexityBoundaryDemo()\n      stdout <- Stdout()\n\n      scores <- [15, 42, 78, 95]\n      for scoreValue in scores\n        stdout.println(analyzeMetrics(scoreValue))","migrationContext":"Java: no combined check (SonarQube checks complexity and length separately). Python: no combined check. C++: no combined check. Rust: no combined check. Go: no combined check. EK9: multiplicative combined check catches functions that pass individual limits but are still too complex for their size.","keywords":["E11020","boundary","clean-code","combined","complexity","decompose","formula","limit","metric","quality","size","statements","threshold"],"primaryTopics":[],"typicalErrors":[{"error":"E50060","correct":"stdout.println(analyzeMetrics(scoreValue))","incorrect":"stdout.println(analyzeMetrics(scoreValue).toUpperCase())","explanation":"String has no toUpperCase() method in EK9. Use upperCase() instead. See ek9 -h E50060 for details."},{"error":"E50060","correct":"classification <- describeLevel(scoreLevel)","incorrect":"classification <- describeLevel(scoreLevel).toUpperCase()","explanation":"String has no toUpperCase() method in EK9. Use upperCase() instead. See ek9 -h E50060 for details."}],"companions":[]}
{"id":734,"category":"Code Quality","question":"How does EK9 detect redundant Boolean comparisons and logical tautologies?","url":"https://ek9.io/qa/QA0734.html","alternatePhrasings":["What is E08084 redundant Boolean comparison in EK9?","What is E08085 logical tautology in EK9?","Why does EK9 reject comparing a Boolean to true or false?"],"answer":"EK9 detects two categories of pointless Boolean logic at compile time.\n\nREDUNDANT BOOLEAN COMPARISON (E08084)\nComparing a Boolean expression directly to true or false is always redundant. 'if flag == true' is identical to 'if flag'. 'if flag == false' is identical to 'if not flag'. The comparison adds nothing and obscures intent.\n\nLOGICAL TAUTOLOGY (E08085)\nAn expression like 'flag or not flag' is always true regardless of what flag is. Similarly 'flag and not flag' is always false. These are structural tautologies or contradictions that indicate a bug, typically a copy-paste error where two different variables were intended.\n\nCORRECT PATTERNS\nUse Boolean values directly in conditions:\n  if isReady             not 'if isReady == true'\n  if not isDone          not 'if isDone == false'\n  if ready or fallback   different variables, not 'ready or not ready'\n\nSee Q318 for self-comparison detection. See Q558 for tautological conditions overview. See Q639 for Boolean patterns without literals.","ek9Example":"defines module qa.codequality.booleantautology\n\n  defines function\n\n    checkEligibility() as pure\n      ->\n        hasAccount as Boolean\n        isVerified as Boolean\n      <- eligible as Boolean: hasAccount and isVerified\n\n    canProceed() as pure\n      ->\n        ready as Boolean\n        fallback as Boolean\n      <- proceed as Boolean: ready or fallback\n\n    describeState() as pure\n      ->\n        active as Boolean\n        locked as Boolean\n      <- description as String: \"unknown\"\n\n      if active and not locked\n        description: \"available\"\n      else if active\n        description: \"locked\"\n      else if not active\n        description: \"inactive\"\n\n  defines program\n\n    BooleanTautologyDemo()\n      stdout <- Stdout()\n\n      eligible <- checkEligibility(hasAccount: true, isVerified: true)\n      stdout.println(`Eligible: ${eligible}`)\n\n      proceed <- canProceed(ready: false, fallback: true)\n      stdout.println(`Proceed: ${proceed}`)\n\n      stdout.println(describeState(active: true, locked: false))\n      stdout.println(describeState(active: true, locked: true))\n      stdout.println(describeState(active: false, locked: false))","migrationContext":"Java: SpotBugs detects some redundant Boolean comparisons. ESLint has no-constant-binary-expression. Rust: clippy has bool_comparison and logic_bug. Python: pylint detects some redundant comparisons. EK9: mandatory compiler error for both redundant Boolean comparisons and logical tautologies.","keywords":["E08084","E08085","boolean","comparison","contradiction","false","logic","quality","redundant","tautology","true"],"primaryTopics":[],"typicalErrors":[{"error":"E50060","correct":"eligible <- checkEligibility(hasAccount: true, isVerified: true)","incorrect":"eligible <- checkEligibility(hasAccount: true, isVerified: true).booleanValue()","explanation":"Boolean has no booleanValue() method in EK9. Boolean values are used directly. See ek9 -h E50060 for details."},{"error":"E50060","correct":"stdout.println(describeState(active: true, locked: false))","incorrect":"stdout.println(describeState(active: true, locked: false).toUpperCase())","explanation":"String has no toUpperCase() method in EK9. Use upperCase() instead. See ek9 -h E50060 for details."}],"companions":[]}
{"id":735,"category":"Code Quality","question":"How does EK9 detect conditions that are always true or always false?","url":"https://ek9.io/qa/QA0735.html","alternatePhrasings":["What is E08086 condition always true in EK9?","What is E08087 condition always false in EK9?","How does EK9 find dead code from constant conditions?"],"answer":"EK9 tracks variable assignments through code paths and detects when a condition is guaranteed to produce the same result every time.\n\nCONDITION ALWAYS TRUE (E08086)\nIf you assign a literal and then compare to the same value, the compiler knows the result. 'x <- 5; if x == 5' is always true. The else branch is dead code. Also detected when an outer condition implies an inner one: 'if count > 10; if count > 6' is always true since count > 10 implies count > 6.\n\nCONDITION ALWAYS FALSE (E08087)\nSimilarly, 'x <- 5; if x == 10' is always false. The if body is dead code. Also detected for contradictory ranges: 'if count > 10 and count < 3' is always false.\n\nWHEN TRACKING RESETS\nThe compiler stops tracking when a variable is reassigned from a function call or mutated by a non-pure call, since the value becomes unknown.\n\nSee Q557 for dead code detection overview. See Q558 for tautological conditions.","ek9Example":"defines module qa.codequality.conditiontruth\n\n  defines function\n\n    classifyScore() as pure\n      -> score as Integer\n      <- label as String: \"average\"\n\n      highThreshold <- 90\n      lowThreshold <- 40\n\n      if score >= highThreshold\n        label: \"excellent\"\n      else if score < lowThreshold\n        label: \"poor\"\n\n    categorizeRange() as pure\n      -> measurement as Integer\n      <- category as String: \"normal\"\n\n      upperBound <- 100\n      lowerBound <- 10\n\n      if measurement > upperBound\n        category: \"above\"\n      else if measurement < lowerBound\n        category: \"below\"\n\n  defines program\n\n    ConditionTruthDemo()\n      stdout <- Stdout()\n\n      stdout.println(classifyScore(95))\n      stdout.println(classifyScore(30))\n      stdout.println(classifyScore(60))\n\n      stdout.println(categorizeRange(150))\n      stdout.println(categorizeRange(5))\n      stdout.println(categorizeRange(50))","migrationContext":"Java: SpotBugs detects some constant conditions. C/C++: compiler warnings for tautological comparisons with -Wtautological-compare. Rust: clippy detects some always-true conditions. Go: go vet detects some unreachable code. EK9: mandatory compiler error with full flow-sensitive tracking across branches.","keywords":["E08086","E08087","always","code","condition","constant","dead","false","flow","quality","tracking","true"],"primaryTopics":[],"typicalErrors":[{"error":"E50060","correct":"stdout.println(classifyScore(95))","incorrect":"stdout.println(classifyScore(95).toUpperCase())","explanation":"String has no toUpperCase() method in EK9. Use upperCase() instead. See ek9 -h E50060 for details."},{"error":"E50060","correct":"stdout.println(categorizeRange(150))","incorrect":"stdout.println(categorizeRange(150).toUpperCase())","explanation":"String has no toUpperCase() method in EK9. Use upperCase() instead. See ek9 -h E50060 for details."}],"companions":[]}
{"id":736,"category":"Code Quality","question":"How does EK9 detect redundant empty and isSet checks?","url":"https://ek9.io/qa/QA0736.html","alternatePhrasings":["What is E08089 isSet always false in EK9?","What is E08092 empty always true in EK9?","What is E08093 empty always false in EK9?"],"answer":"EK9 tracks variable state through data flow and detects three categories of redundant checks.\n\nISSET ALWAYS FALSE (E08089)\nIf a variable is declared unset (with ?) and never assigned a value, checking it with ? is always false. The code inside the if block is dead code.\n\nEMPTY ALWAYS TRUE (E08092)\nIf a collection is created with no elements and not modified before an empty check, the check is always true. An empty Optional is also always empty.\n\nEMPTY ALWAYS FALSE (E08093)\nIf a collection is created from a literal with elements, an empty check is always false. An Optional created with a value is never empty.\n\nCORRECT PATTERNS\nAssign a value before checking isSet. Populate collections before checking empty. Use function returns where the state is genuinely unknown.\n\nSee Q559 for redundant isSet overview. See Q640 for collection isSet patterns.","ek9Example":"defines module qa.codequality.emptyissetflow\n\n  defines function\n\n    findName()\n      -> key as String\n      <- result as String: String()\n\n      if key == \"admin\"\n        result: \"Administrator\"\n\n    describeItems()\n      -> items as List of String\n      <- description as String: \"empty\"\n\n      if not items empty\n        description: `${length items} items`\n\n  defines program\n\n    EmptyIsSetFlowDemo()\n      stdout <- Stdout()\n\n      //isSet check on function return - genuinely needed\n      name <- findName(\"admin\")\n      if name?\n        stdout.println(`Found: ${name}`)\n\n      //empty check on function return - genuinely needed\n      items <- [\"alpha\", \"beta\"]\n      stdout.println(describeItems(items))\n\n      emptyList <- List() of String\n      stdout.println(describeItems(emptyList))","migrationContext":"Java: no built-in detection, optional IDE inspections. Rust: clippy detects some redundant Option checks. Python: pylint detects some unreachable code. Go: go vet detects some unreachable code. EK9: mandatory compiler error with full data flow tracking for isSet and empty checks.","keywords":["E08089","E08092","E08093","always","code","collection","dead","empty","flow","isset","never","optional","quality","redundant"],"primaryTopics":[],"typicalErrors":[{"error":"E50060","correct":"name <- findName(\"admin\")","incorrect":"name <- findName(\"admin\").toUpperCase()","explanation":"String has no toUpperCase() method in EK9. Use upperCase() instead. See ek9 -h E50060 for details."},{"error":"E50060","correct":"name <- findName(\"admin\")","incorrect":"name <- findName(\"admin\").getValue()","explanation":"String has no getValue() method. findName() already returns a String. See ek9 -h E50060 for details."},{"error":"E50060","correct":"stdout.println(describeItems(items))","incorrect":"stdout.println(items.toString())","explanation":"List has no toString() method. Use string interpolation or the $ operator. See ek9 -h E50060 for details."}],"companions":[]}
{"id":737,"category":"Code Quality","question":"Why does EK9 reject reassignment in pure functions and parameter reassignment?","url":"https://ek9.io/qa/QA0737.html","alternatePhrasings":["What is E08100 reassignment in pure context?","What is E08110 parameter reassignment in EK9?","How do I modify values in a pure function?"],"answer":"EK9 enforces two reassignment restrictions to prevent hidden state changes.\n\nPURE CONTEXT REASSIGNMENT (E08100)\nPure functions cannot use ':=' to reassign variables. Pure functions should compute results, not accumulate state. Use ':=?' (guarded assignment, only sets if unset) or restructure the logic to use return variable assignment.\n\nPARAMETER REASSIGNMENT (E08110)\nFunction and method parameters are read-only. You cannot reassign a parameter with ':='. Parameters represent the caller's input and modifying them obscures the data flow. Instead, copy the parameter to a local variable and modify the copy.\n\nWHY ENFORCED\nThese restrictions exist because reassignment creates hidden state changes that are hard to reason about. Pure functions should be deterministic and side-effect free. Parameter immutability prevents confusion about whether modifications are visible to the caller.\n\nSee Q678 for purity contracts. See Q635 for pure mutation restrictions.","ek9Example":"defines module qa.codequality.pureparamreassign\n\n  defines function\n\n    classifyScore() as pure\n      -> score as Integer\n      <- label as String: \"average\"\n\n      highThreshold <- 90\n      lowThreshold <- 40\n\n      if score >= highThreshold\n        label: \"excellent\"\n      else if score < lowThreshold\n        label: \"poor\"\n\n    doubleValue() as pure\n      -> input as Integer\n      <- result as Integer: input * 2\n\n    formatName()\n      -> rawName as String\n      <- formatted as String: rawName\n\n      localName <- rawName\n      localName := \"Mr. \" + localName\n      formatted: localName\n\n  defines program\n\n    PureParamReassignDemo()\n      stdout <- Stdout()\n\n      stdout.println(classifyScore(95))\n      stdout.println(classifyScore(30))\n      stdout.println(classifyScore(60))\n\n      stdout.println(`Doubled: ${doubleValue(21)}`)\n      stdout.println(formatName(\"Smith\"))","migrationContext":"Java: parameters can be reassigned (but final prevents it). Kotlin: function parameters are val (immutable) by default. Rust: parameters are immutable by default, require mut. Python: parameters can be reassigned freely. Go: parameters can be reassigned freely. EK9: parameters always immutable, pure context bans all reassignment.","keywords":["E08100","E08110","context","immutable","parameter","pure","quality","readonly","reassignment","restriction"],"primaryTopics":[],"typicalErrors":[{"error":"E50060","correct":"stdout.println(classifyScore(95))","incorrect":"stdout.println(classifyScore(95).toUpperCase())","explanation":"String has no toUpperCase() method in EK9. Use upperCase() instead. See ek9 -h E50060 for details."},{"error":"E50060","correct":"stdout.println(formatName(\"Smith\"))","incorrect":"stdout.println(formatName(\"Smith\").toUpperCase())","explanation":"String has no toUpperCase() method in EK9. Use upperCase() instead. See ek9 -h E50060 for details."},{"error":"E08110","correct":"localName <- rawName\n      localName := \"Mr. \" + localName","incorrect":"rawName := \"Mr. \" + rawName","explanation":"Parameters are read-only in EK9. Copy the parameter to a local variable first, then modify the copy. See ek9 -h E08110 for details."},{"error":"E50060","correct":"stdout.println(classifyScore(95))","incorrect":"stdout.println(classifyScore(95).toString())","explanation":"String has no toString() method. classifyScore() already returns a String. See ek9 -h E50060 for details."}],"companions":[]}
{"id":738,"category":"Code Quality","question":"Why does EK9 reject mutation operators on function types?","url":"https://ek9.io/qa/QA0738.html","alternatePhrasings":["What is E08240 mutation on function type?","Why can I not use copy operator on a function variable?","What operators work on function types in EK9?"],"answer":"Function types are callable references, not data containers. Mutation operators like :=: (copy), :~: (merge), :^: (replace), += (add-assign), and -= (subtract-assign) are not applicable to function types.\n\nWHAT WORKS ON FUNCTIONS\nOnly these operations are valid on function variables:\n- := (assignment) changes which function is referenced\n- ? (isSet) checks if a function reference has been assigned\n\nWHY MUTATION IS BANNED\nFunctions represent behaviour, not state. Copying, merging, or replacing functions has no meaningful semantics. Assignment (:=) changes the reference to point at a different function. That is fundamentally different from :=: which copies the internal state of an object.\n\nCOMMON MISTAKE\nDevelopers coming from languages where lambdas are objects (Java, Python) try to use copy semantics on function references. In EK9, simply use := to reassign a function variable to a different function.\n\nSee Q49 for function basics. See Q52 for dynamic functions. See Q596 for function vs method distinction.","ek9Example":"defines module qa.codequality.functionmutation\n\n  defines function\n\n    Formatter as abstract\n      -> text as String\n      <- result as String?\n\n    upperFormatter() is Formatter\n      -> text as String\n      <- result as String: text.upperCase()\n\n    bracketFormatter() is Formatter\n      -> text as String\n      <- result as String: `[${text}]`\n\n    applyFormatter()\n      ->\n        formatter as Formatter\n        text as String\n      <- result as String: formatter(text)\n\n  defines program\n\n    FunctionMutationDemo()\n      stdout <- Stdout()\n\n      //Function assignment with :=\n      handler as Formatter: upperFormatter\n      stdout.println(applyFormatter(handler, \"hello\"))\n\n      //Reassignment with := is valid\n      handler := bracketFormatter\n      stdout.println(applyFormatter(handler, \"world\"))\n\n      //isSet check with ? is valid\n      stdout.println(`Handler set: ${handler?}`)","migrationContext":"Java: lambdas are objects with copy semantics via clone. Python: functions are objects that can be copied. JavaScript: functions are objects with spread/assign. Rust: closures implement traits, can be cloned if contents are Clone. EK9: function types are pure references, only := and ? are valid.","keywords":["E08240","callable","copy","function","merge","mutation","operator","quality","reference","replace","type"],"primaryTopics":[],"typicalErrors":[{"error":"E08240","correct":"handler := bracketFormatter","incorrect":"handler :=: bracketFormatter","explanation":"Function types do not support the copy operator :=:. Functions are references, not data containers. Use := (assignment) to change which function is referenced. See ek9 -h E08240 for details."},{"error":"E08240","correct":"handler as Formatter: upperFormatter","incorrect":"handler as Formatter: upperFormatter\n      handler :~: bracketFormatter","explanation":"Function types do not support the merge operator :~:. Use := (assignment) to reassign the function reference. See ek9 -h E08240 for details."},{"error":"E08240","correct":"stdout.println(applyFormatter(handler, \"hello\"))","incorrect":"handler :^: bracketFormatter\n      stdout.println(applyFormatter(handler, \"hello\"))","explanation":"Function types do not support the replace operator :^:. Use := (assignment) to change the function reference. See ek9 -h E08240 for details."}],"companions":[]}
{"id":739,"category":"Code Quality","question":"How does EK9 detect duplicate names and properties in type hierarchies?","url":"https://ek9.io/qa/QA0739.html","alternatePhrasings":["What is E02010 duplicate name in EK9?","What is E02020 duplicate property for JSON in EK9?","Why does EK9 reject properties with the same name in parent and child?"],"answer":"EK9 detects two categories of property naming collisions in type hierarchies at compile time.\n\nDUPLICATE NAME (E02010)\nA child type cannot redeclare a property with the same name as a parent property. This prevents field shadowing which causes ambiguity in method resolution and state management.\n\nDUPLICATE PROPERTY FOR JSON (E02020)\nWhen a type hierarchy has two properties with the same name at different levels and uses the JSON serialization operator ($$), the compiler cannot determine which to include. EK9 rejects this at compile time rather than producing ambiguous JSON output.\n\nCORRECT PATTERNS\nUse distinct names for properties at each level. If a child needs different data, use a different property name. If the parent property is sufficient, access it through inherited methods.\n\nSee Q93 for class definition. See Q97 for records vs classes.","ek9Example":"defines module qa.codequality.duplicatenames\n\n  defines class\n\n    Employee\n      name <- String()\n      role <- String()\n\n      Employee()\n        ->\n          name as String\n          role as String\n        this.name: name\n        this.role: role\n\n      describe()\n        <- rtn as String: `${name} (${role})`\n\n      default operator\n\n  defines program\n\n    DuplicateNamesDemo()\n      stdout <- Stdout()\n\n      emp <- Employee(\"Alice\", \"Engineer\")\n      stdout.println(emp.describe())\n      stdout.println(`Equal: ${emp == Employee(\"Alice\", \"Engineer\")}`)","migrationContext":"Java: field shadowing is allowed but produces warnings. Python: attribute shadowing is implicit. Rust: no inheritance, no shadowing. Go: embedding can shadow fields. Kotlin: property override requires explicit 'override' keyword. EK9: duplicate names rejected at compile time, no shadowing allowed.","keywords":["E02010","E02020","collision","duplicate","hierarchy","json","name","property","quality","shadow"],"primaryTopics":[],"typicalErrors":[{"error":"E50060","correct":"stdout.println(emp.describe())","incorrect":"stdout.println(emp.toString())","explanation":"EK9 has no toString() method. Use string interpolation or the class's describe() method. See ek9 -h E50060 for details."},{"error":"E50060","correct":"emp <- Employee(\"Alice\", \"Engineer\")","incorrect":"emp <- Employee(\"Alice\", \"Engineer\").getName()","explanation":"Employee has no getName() method. Use describe() to get employee information. See ek9 -h E50060 for details."}],"companions":[]}
{"id":740,"category":"Code Quality","question":"How does EK9 detect duplicate trait references and service path conflicts?","url":"https://ek9.io/qa/QA0740.html","alternatePhrasings":["What is E02050 duplicate trait reference in EK9?","What is E02070 service HTTP path duplicated in EK9?","What is E02080 delegate and method name clash in EK9?"],"answer":"EK9 detects three categories of structural duplication at compile time.\n\nDUPLICATE TRAIT REFERENCE (E02050)\nListing the same trait multiple times in a 'with trait of' clause is redundant and indicates a mistake. Each trait should appear exactly once.\n\nSERVICE PATH DUPLICATION (E02070)\nTwo service operations cannot share identical HTTP method and path pattern combinations. Even if the path variable names differ, the routing structure is the same and creates ambiguity.\n\nDELEGATE METHOD NAME CLASH (E02080)\nA function delegate field cannot have the same name as a method in the same class. The compiler cannot distinguish between calling the delegate and calling the method.\n\nSee Q93 for class traits. See Q657 for service URI mapping.","ek9Example":"defines module qa.codequality.duplicatetraits\n\n  defines trait\n\n    Printable\n      show() as abstract\n        <- rtn as String?\n\n    Describable\n      describe() as abstract\n        <- rtn as String?\n\n  defines class\n\n    Item with trait of Printable, Describable\n      name <- String()\n\n      Item()\n        -> name as String\n        this.name: name\n\n      override show()\n        <- rtn as String: name\n\n      override describe()\n        <- rtn as String: `Item: ${name}`\n\n      default operator\n\n  defines program\n\n    DuplicateTraitsDemo()\n      stdout <- Stdout()\n\n      item <- Item(\"Widget\")\n      stdout.println(item.show())\n      stdout.println(item.describe())","migrationContext":"Java: duplicate interface implementation silently ignored. Python: no trait concept, multiple inheritance. Rust: duplicate trait bounds produce errors. Go: duplicate interface embedding is valid. Spring Boot: duplicate path mappings fail at runtime. EK9: all three detected at compile time.","keywords":["E02050","E02070","E02080","clash","delegate","duplicate","method","path","quality","service","trait"],"primaryTopics":[],"typicalErrors":[{"error":"E02050","correct":"Item with trait of Printable, Describable","incorrect":"Item with trait of Printable, Printable","explanation":"The same trait 'Printable' is listed twice. Each trait should appear only once in the 'with trait of' clause. See ek9 -h E02050 for details."},{"error":"E50060","correct":"stdout.println(item.show())","incorrect":"stdout.println(item.toString())","explanation":"EK9 has no toString() method. Use the show() method or string interpolation. See ek9 -h E50060 for details."},{"error":"E50060","correct":"stdout.println(item.describe())","incorrect":"stdout.println(item.getDescription())","explanation":"The method is describe(), not getDescription(). EK9 does not use Java-style getter naming. See ek9 -h E50060 for details."}],"companions":[]}
{"id":741,"category":"Code Quality","question":"How does EK9 detect reference conflicts and unresolved references?","url":"https://ek9.io/qa/QA0741.html","alternatePhrasings":["What is E03020 references conflict in EK9?","What is E03030 reference does not resolve in EK9?","Why does my module reference fail to compile?"],"answer":"EK9 validates all module references at compile time and detects two categories of problems.\n\nREFERENCES CONFLICT (E03020)\nThe same symbol cannot be referenced twice in a references block. Each external symbol should appear exactly once. Duplicating a reference serves no purpose and may indicate a copy-paste error.\n\nREFERENCE DOES NOT RESOLVE (E03030)\nA reference must point to an existing module and symbol. If the module path or symbol name is misspelled, or the module does not exist, the compiler rejects the reference. This catches typos and stale references early.\n\nCORRECT PATTERNS\nEnsure each reference appears once and points to a valid module::Symbol. Use fully qualified inline access as an alternative to the references block.\n\nSee Q726 for module reference syntax. See Q142 for cross-module constants.","ek9Example":"defines module qa.codequality.referenceconflicts\n\n  defines function\n\n    formatItem() as pure\n      -> text as String\n      <- rtn as String: `[${text}]`\n\n  defines program\n\n    ReferenceConflictsDemo()\n      stdout <- Stdout()\n\n      //Direct function call within same module\n      result <- formatItem(\"hello\")\n      stdout.println(result)\n      stdout.println(formatItem(\"world\"))","migrationContext":"Java: duplicate imports produce warnings. Python: duplicate imports silently overwrite. Rust: duplicate use statements produce errors. Go: unused imports are errors. EK9: duplicate references and unresolved references are both compile-time errors.","keywords":["E03020","E03030","conflict","duplicate","import","missing","module","quality","reference","resolve"],"primaryTopics":[],"typicalErrors":[{"error":"E50060","correct":"result <- formatItem(\"hello\")","incorrect":"result <- formatItem(\"hello\").getValue()","explanation":"String has no getValue() method. formatItem() already returns a String. See ek9 -h E50060 for details."},{"error":"E50060","correct":"stdout.println(formatItem(\"world\"))","incorrect":"stdout.println(formatItem(\"world\").toString())","explanation":"String has no toString() method in EK9. The function already returns a String. See ek9 -h E50060 for details."}],"companions":[]}
{"id":742,"category":"Code Quality","question":"How does EK9 detect when a function type or generic type is used incorrectly?","url":"https://ek9.io/qa/QA0742.html","alternatePhrasings":["What is E04040 type must be function in EK9?","What is E04070 not a template in EK9?","Why does EK9 reject my stream call on a non-function type?"],"answer":"EK9 validates type usage in two specific contexts.\n\nTYPE MUST BE FUNCTION (E04040)\nThe 'call' and 'async' stream operators require function types. If you pipe non-function values (like integers or strings) into a call operation, the compiler rejects it. Only function references can be called.\n\nNOT A TEMPLATE (E04070)\nThe 'of' syntax (e.g., List of String) is only valid for generic/template types. Applying type parameters to a non-generic type like Integer or a user-defined non-generic class is an error.\n\nCORRECT PATTERNS\nFor stream call operations, ensure the pipeline contains function references. For generic types, only use 'of' with types designed to be parameterized (List, Dict, Optional, Result, etc.).\n\nSee Q707 for consumer/acceptor patterns. See Q642 for generic constructor inference.","ek9Example":"defines module qa.codequality.typefunctiongeneric\n\n  defines function\n\n    formatUpper() as pure\n      -> text as String\n      <- rtn as String: text.upperCase()\n\n    formatBracket() as pure\n      -> text as String\n      <- rtn as String: `[${text}]`\n\n  defines program\n\n    TypeFunctionGenericDemo()\n      stdout <- Stdout()\n\n      //Correct use of generic types\n      names <- List() of String\n      names += \"Alice\"\n      names += \"Bob\"\n      stdout.println(`Names: ${length names}`)\n\n      //Functions used correctly\n      stdout.println(formatUpper(\"hello\"))\n      stdout.println(formatBracket(\"world\"))","migrationContext":"Java: type safety on generics via erasure, runtime ClassCastException possible. Python: no compile-time checking. Rust: trait bounds enforce generic constraints. Go: type parameters require interface constraints. EK9: compile-time enforcement of function types and generic type parameters.","keywords":["E04040","E04070","call","function","generic","parameter","quality","stream","template","type"],"primaryTopics":[],"typicalErrors":[{"error":"E50060","correct":"stdout.println(formatUpper(\"hello\"))","incorrect":"stdout.println(formatUpper(\"hello\").getValue())","explanation":"String has no getValue() method. formatUpper() already returns a String. See ek9 -h E50060 for details."},{"error":"E06010","correct":"names <- List() of String","incorrect":"names <- List()","explanation":"List is a generic type that requires a type parameter. Use 'List() of String' for explicit typing. See ek9 -h E06010 for details."}],"companions":[]}
{"id":743,"category":"Code Quality","question":"What are the restrictions on this and super in EK9?","url":"https://ek9.io/qa/QA0743.html","alternatePhrasings":["What is E05040 super for Any not required in EK9?","What is E05090 inappropriate use of this or super in EK9?","Why does EK9 reject super() in my constructor?"],"answer":"EK9 enforces specific rules about how 'this' and 'super' can be used.\n\nSUPER FOR ANY NOT REQUIRED (E05040)\nAll EK9 classes implicitly extend 'Any'. Calling super() in a constructor when there is no explicit parent class is unnecessary and rejected. Only call super() when the class explicitly extends another class.\n\nINAPPROPRIATE USE OF THIS OR SUPER (E05090)\nYou cannot directly assign to 'this' or 'super' using ':=', ':=?', or ':'. These would attempt to replace the object pointer itself, which is not meaningful. Use ':=:' (copy) or ':~:' (merge) instead to copy state from another object.\n\nALLOWED OPERATIONS\n- this :=: other  (copy state from other)\n- this :~: other  (merge state from other)\n- $this           (string conversion)\n- this?           (isSet check)\n\nSee Q580 for this delegation. See Q581 for super delegation.","ek9Example":"defines module qa.codequality.thissuperrestrictions\n\n  defines class\n\n    Base as open\n      label <- String()\n\n      Base()\n        -> label as String\n        this.label: label\n\n      describe()\n        <- rtn as String: label\n\n      default operator\n\n    Child extends Base\n      role <- String()\n\n      Child()\n        ->\n          label as String\n          role as String\n        //super() IS valid here because Child explicitly extends Base\n        super(label)\n        this.role: role\n\n      override describe()\n        <- rtn as String: `${super.describe()} (${role})`\n\n      default operator\n\n  defines program\n\n    ThisSuperDemo()\n      stdout <- Stdout()\n\n      child <- Child(\"Alice\", \"Engineer\")\n      stdout.println(child.describe())\n\n      base <- Base(\"Bob\")\n      stdout.println(base.describe())","migrationContext":"Java: super() implicitly inserted if omitted. Python: super().__init__() must be called explicitly. Rust: no super concept. Go: no inheritance. Kotlin: super() implicitly called. EK9: super() only valid with explicit parent, this/super cannot be reassigned.","keywords":["E05040","E05090","assignment","constructor","copy","merge","quality","restriction","super","this"],"primaryTopics":[],"typicalErrors":[{"error":"E05040","correct":"Base()\n        -> label as String\n        this.label: label","incorrect":"Base()\n        -> label as String\n        super()\n        this.label: label","explanation":"Base has no explicit parent, so calling super() is unnecessary. All classes implicitly extend Any. Remove the super() call. See ek9 -h E05040 for details."},{"error":"E50060","correct":"stdout.println(child.describe())","incorrect":"stdout.println(child.toString())","explanation":"EK9 has no toString() method. Use the describe() method or string interpolation. See ek9 -h E50060 for details."},{"error":"E50060","correct":"stdout.println(base.describe())","incorrect":"stdout.println(base.getLabel())","explanation":"The method is describe(), not getLabel(). EK9 does not use Java-style getter naming. See ek9 -h E50060 for details."},{"error":"E05090","correct":"        this.label: label","incorrect":"        this: label","explanation":"Cannot assign directly to 'this'. Use 'this.fieldName' to access a specific field, or use :=:, :~:, += etc. for aggregate operations. See ek9 -h E05090 for details."}],"companions":[]}
{"id":744,"category":"Code Quality","question":"How does EK9 validate generic constructors and sealed type allow only lists?","url":"https://ek9.io/qa/QA0744.html","alternatePhrasings":["What is E05200 incompatible genus constructor in EK9?","What is E05250 abstract in allow only in EK9?","Why does EK9 reject abstract classes in sealed trait allow only lists?"],"answer":"EK9 validates two specific type constraint patterns at compile time.\n\nINCOMPATIBLE GENUS CONSTRUCTOR (E05200)\nWhen instantiating a generic type, the constructor arguments must be compatible with the type parameters. If the generic type requires a specific kind of constructor (e.g., a copy constructor from the type parameter), the actual arguments must satisfy that constraint.\n\nABSTRACT IN ALLOW ONLY (E05250)\nSealed traits use 'allow only' to restrict which concrete types can implement them. Abstract classes cannot appear in this list because they can never be instantiated as concrete runtime types. Only concrete classes that can actually exist at runtime belong in the allow only list.\n\nCORRECT PATTERNS\nFor generics, ensure constructor arguments match type parameter requirements. For sealed traits, list only concrete classes in the allow only clause.\n\nSee Q679 for sealed allow only. See Q642 for generic constructor inference.","ek9Example":"defines module qa.codequality.genericsealedconstraints\n\n  defines class\n\n    ConcreteAlpha\n      doWork()\n        content <- \"Alpha\"\n        require content?\n\n      default operator\n\n    ConcreteBeta\n      doWork()\n        content <- \"Beta\"\n        require content?\n\n      default operator\n\n  defines trait\n\n    //Correct: only concrete classes in allow only list\n    Processable allow only ConcreteAlpha, ConcreteBeta\n      process() as abstract\n\n  defines program\n\n    GenericSealedDemo()\n      stdout <- Stdout()\n\n      //Correct generic usage\n      names <- List() of String\n      names += \"Alice\"\n      names += \"Bob\"\n      stdout.println(`Names: ${length names}`)","migrationContext":"Java: generic constraints via bounded wildcards, no sealed trait equivalent until Java 17. Kotlin: sealed classes restrict subclassing. Rust: trait bounds on generics. Swift: no sealed traits. EK9: compile-time enforcement of both generic constructor compatibility and sealed trait allow only lists.","keywords":["E05200","E05250","abstract","allow","constraint","constructor","generic","only","quality","sealed"],"primaryTopics":[],"typicalErrors":[{"error":"E50010","correct":"Processable allow only ConcreteAlpha, ConcreteBeta","incorrect":"Processable allow only ConcreteAlpha, AbstractWorker, ConcreteBeta","explanation":"AbstractWorker does not exist in this module. Adding an unresolved type to the allow only list triggers E50010. See ek9 -h E50010 for details."},{"error":"E06010","correct":"names <- List() of String","incorrect":"names <- List()","explanation":"List is a generic type that requires a type parameter. Use 'List() of String' for explicit typing. See ek9 -h E06010 for details."}],"companions":[]}
{"id":745,"category":"Code Quality","question":"How does EK9 validate function and method parameters?","url":"https://ek9.io/qa/QA0745.html","alternatePhrasings":["What is E06250 named parameters must match in EK9?","What is E06270 function parameter mismatch in EK9?","What is E06310 require no arguments in EK9?"],"answer":"EK9 validates function and method parameters comprehensively at compile time.\n\nNAMED PARAMETERS MUST MATCH (E06250)\nWhen using named parameters in a call, the names must match the parameter names in the function definition. Misspelled or incorrect parameter names are rejected.\n\nFUNCTION PARAMETER MISMATCH (E06270)\nArgument types must match parameter types. EK9 has no implicit type conversions. Passing an Integer where a String is expected is a compile-time error. Use explicit conversion operators like $ for string conversion.\n\nREQUIRE NO ARGUMENTS (E06310)\nStream call and async operators require zero-argument supplier functions. Piping a function that takes parameters into a call operator is rejected.\n\nINVALID NUMBER OF PARAMETERS (E06320)\nDispatcher overloads must have matching parameter counts. All handler methods must accept the same number of parameters as the dispatcher entry point.\n\nSee Q694 for named arguments patterns. See Q596 for function parameters.","ek9Example":"defines module qa.codequality.parametervalidation\n\n  defines function\n\n    greet() as pure\n      -> name as String\n      <- rtn as String: `Hello, ${name}`\n\n    createUser() as pure\n      ->\n        name as String\n        age as Integer\n      <- rtn as String: `${name} (age ${age})`\n\n    doubleIt() as pure\n      -> number as Integer\n      <- rtn as Integer: number * 2\n\n  defines program\n\n    ParameterValidationDemo()\n      stdout <- Stdout()\n\n      //Correct parameter types\n      stdout.println(greet(\"Alice\"))\n      stdout.println(createUser(\"Bob\", 25))\n      stdout.println(`Doubled: ${doubleIt(21)}`)\n\n      //Correct: explicit conversion with $\n      count <- 42\n      stdout.println(greet($count))","migrationContext":"Java: implicit widening conversions allowed. Python: duck typing, no compile-time checks. Rust: no implicit conversions, explicit Into/From traits. Go: no implicit conversions. Kotlin: limited implicit conversions. EK9: zero implicit conversions, all parameter types checked at compile time.","keywords":["E06250","E06270","E06310","E06320","argument","conversion","mismatch","named","parameter","quality","type","validation"],"primaryTopics":[],"typicalErrors":[{"error":"E06270","correct":"greet($count)","incorrect":"greet(count)","explanation":"Function 'greet' expects a String but received an Integer. EK9 has no implicit type conversion. Use the $ operator to convert to String explicitly. See ek9 -h E06270 for details."},{"error":"E50060","correct":"stdout.println(greet(\"Alice\"))","incorrect":"stdout.println(greet(\"Alice\").toUpperCase())","explanation":"String has no toUpperCase() method. Use upperCase() instead. See ek9 -h E50060 for details."},{"error":"E50060","correct":"stdout.println(createUser(\"Bob\", 25))","incorrect":"stdout.println(createUser(\"Bob\", 25).toString())","explanation":"String has no toString() method. createUser() already returns a String. See ek9 -h E50060 for details."},{"error":"E50060","correct":"stdout.println(`Doubled: ${doubleIt(21)}`)","incorrect":"stdout.println(doubleIt(21).toString())","explanation":"Integer has no toString() method. Use string interpolation or the $ operator. See ek9 -h E50060 for details."}],"companions":[]}
{"id":746,"category":"Enumerations","question":"How do I migrate Swift enum associated values to EK9?","url":"https://ek9.io/qa/QA0746.html","alternatePhrasings":["What is the EK9 equivalent of Swift enum with associated values?","How do I model Swift's enum cases with data in EK9?","How do I replace Swift associated values with EK9 composition?"],"answer":"Swift enums can carry data per variant (associated values). EK9 enums are pure value types with no fields or methods. The EK9 equivalent uses enum + Dict for data, or enum + class hierarchy for complex variants.\n\nSWIFT PATTERN: ASSOCIATED VALUES\nSwift lets each enum case carry different data:\n  enum Shape {\n    case circle(radius: Double)\n    case rectangle(width: Double, height: Double)\n  }\nSwift extracts data via pattern matching in switch.\n\nEK9 PATTERN 1: ENUM + DICT FOR SIMPLE DATA\nWhen each variant maps to a single value, use a Dict:\n  defines type\n    Colour: Red, Green, Blue\n  labels <- {Colour.Red: \"#FF0000\", Colour.Green: \"#00FF00\", Colour.Blue: \"#0000FF\"}\nThis separates identity (enum) from data (Dict), keeping both independently testable and serialisable.\n\nEK9 PATTERN 2: TRAIT HIERARCHY FOR COMPLEX VARIANTS\nWhen variants carry different shaped data (like Swift's Shape example), use a trait with classes:\n  defines trait\n    Shape\n      area() as pure abstract\n        <- rtn as Float?\n  defines class\n    Circle with trait of Shape\n      radius <- 0.0\n      override area() as pure\n        <- rtn as Float: 3.14159 * radius * radius\n    Rectangle with trait of Shape\n      ...\nEach class carries its own data. The trait defines the shared contract.\n\nWHY COMPOSITION IS SUPERIOR\nSwift's associated values tightly couple identity and data. EK9's separation gives:\n- Serialisability: enums convert to JSON automatically, associated values need custom Codable\n- Testability: functions and Dict entries are independently testable\n- Extensibility: add new data mappings without modifying the enum\n- Quality: the compiler enforces metrics on each function independently\n\nSee Q100 for enum vs Java comparison. See Q219 for auto-generated operators. See Q225 for the composition pattern in detail. See Q106 for traits. See Q109 for composition over inheritance.","ek9Example":"defines module qa.enums.swiftmigration\n\n  defines type\n\n    Colour\n      Red\n      Green\n      Blue\n\n    Shape\n      Circle\n      Rectangle\n      Triangle\n\n  defines trait\n\n    <?-\n      Shared contract for shapes with area calculation.\n      This replaces Swift's enum with associated values.\n    -?>\n    Measurable\n      area() as pure abstract\n        <- rtn as Float?\n\n  defines class\n\n    CircleShape with trait of Measurable\n      radius <- 0.0\n\n      CircleShape()\n        -> radius as Float\n        this.radius: radius\n\n      override area() as pure\n        <- rtn as Float: 3.14159 * radius * radius\n\n      default operator\n\n    RectangleShape with trait of Measurable\n      width <- 0.0\n      height <- 0.0\n\n      RectangleShape()\n        ->\n          width as Float\n          height as Float\n        this.width: width\n        this.height: height\n\n      override area() as pure\n        <- rtn as Float: width * height\n\n      default operator\n\n  defines function\n\n    <?-\n      Maps shape type to a description string.\n      This replaces Swift's associated value extraction.\n    -?>\n    describeShape() as pure\n      -> shapeType as Shape\n      <- description as String: switch shapeType\n        <- rtn as String: String()\n        case Shape.Circle\n          rtn: \"A round shape\"\n        case Shape.Rectangle\n          rtn: \"A four-sided shape\"\n        case Shape.Triangle\n          rtn: \"A three-sided shape\"\n        default\n          rtn: \"Unknown shape\"\n\n  defines program\n\n    SwiftEnumMigrationDemo()\n      stdout <- Stdout()\n\n      // === PATTERN 1: ENUM + DICT FOR SIMPLE DATA ===\n\n      hexCodes <- {Colour.Red: \"#FF0000\", Colour.Green: \"#00FF00\", Colour.Blue: \"#0000FF\"}\n\n      for colour in Colour\n        code <- hexCodes.getOrDefault(colour, \"unknown\")\n        stdout.println(`${colour}: ${code}`)\n\n      // === ENUM + FUNCTION FOR BEHAVIOUR ===\n\n      for shape in Shape\n        stdout.println(describeShape(shape))\n\n      // === PATTERN 2: TRAIT HIERARCHY FOR COMPLEX VARIANTS ===\n\n      circle <- CircleShape(5.0)\n      rect <- RectangleShape(4.0, 6.0)\n\n      stdout.println(`Circle area: ${circle.area()}`)\n      stdout.println(`Rectangle area: ${rect.area()}`)","migrationContext":"Swift: enum cases carry associated values (case circle(radius: Double)), extracted via pattern matching (case .circle(let r)), powerful but couples data to identity. Rust: enum variants with fields, extracted via match arms. Java: enum constants with fields and constructors. Kotlin: sealed class with data class variants. EK9: enums are pure values, use Dict for simple data mapping and trait + class hierarchy for complex variant data, separation of concerns.","keywords":["associated","composition","data","dict","enum","enumeration","migrate","pattern","swift","trait","values","variant"],"primaryTopics":[],"typicalErrors":[{"error":"E50060","correct":"stdout.println(describeShape(shape))","incorrect":"stdout.println(describeShape(shape).toUpperCase())","explanation":"String has no toUpperCase() method in EK9. Use upperCase() instead. See ek9 -h E50060 for details."},{"error":"E50060","correct":"code <- hexCodes.getOrDefault(colour, \"unknown\")","incorrect":"code <- hexCodes.getOrDefault(colour, \"unknown\").toUpperCase()","explanation":"String has no toUpperCase() method in EK9. Use upperCase() instead. See ek9 -h E50060 for details."}],"companions":[]}
{"id":747,"category":"Safe Value Access","question":"How do I migrate Swift optional binding chains to EK9?","url":"https://ek9.io/qa/QA0747.html","alternatePhrasings":["What is the EK9 equivalent of Swift if let?","How do I translate Swift guard let to EK9?","How do I replace Swift optional chaining in EK9?","What replaces Swift's force unwrap in EK9?"],"answer":"Swift has five optional unwrapping patterns. EK9 replaces all of them with guard expressions and coalescing operators, with zero runtime crash risk.\n\nSWIFT PATTERN 1: IF LET (SINGLE BINDING)\n  Swift:  if let name = getName() { use(name) }\n  EK9:    if name <- getName()\n            use(name)\nEK9's guard expression is the direct equivalent. The variable only exists inside the if block. Both skip the block when the value is absent or unset.\n\nSWIFT PATTERN 2: IF LET CHAIN (MULTIPLE BINDINGS)\n  Swift:  if let a = getA(), let b = getB() { use(a, b) }\n  EK9:    if first <- getA()\n            if second <- getB()\n              use(first, second)\nSwift chains multiple bindings in one if. EK9 uses nested guards. Each level adds a guard check.\n\nSWIFT PATTERN 3: GUARD LET (EARLY EXIT)\n  Swift:  guard let name = getName() else { return nil }\n  EK9:    if name <- getName()\n            processName(name)\nEK9 has no return statement. Instead of guard-else-return, put the success path inside the if block. The else block (or simply falling through) handles the failure case.\n\nSWIFT PATTERN 4: NIL COALESCING\n  Swift:  let name = getName() ?? \"default\"\n  EK9:    name <- getName() ?: \"default\"\nSwift's ?? is a memory-level nil check. EK9's ?: checks isSet (more thorough). EK9 also has ?? for memory-level checks, plus <? and >? for coalescing comparisons.\n\nSWIFT PATTERN 5: FORCE UNWRAP\n  Swift:  let name = getName()!\n  EK9:    (no equivalent: force unwrap does not exist)\nEK9 has no force unwrap. There is no escape hatch. You must use a guard or coalescing operator. This eliminates the entire category of force-unwrap crashes.\n\nSWIFT PATTERN 6: OPTIONAL CHAINING\n  Swift:  let len = person?.address?.street?.count\n  EK9:    if person <- getPerson()\n            if addr <- person.address()\n              streetLen <- length addr.street()\nEK9 uses nested guards instead of ?. chaining. Each guard is explicit and checked by the compiler.\n\nSee Q47 for Optional basics. See Q74 for if guard patterns. See Q75 for switch guards. See Q79 for guarded assignment. See Q168 for fallback values. See Q243 for coalescing operators.","ek9Example":"defines module qa.safeaccess.swiftoptional\n\n  defines function\n\n    <?-\n      Simulates a lookup that might not find a value.\n    -?>\n    findUser() as pure\n      -> userId as Integer\n      <- rtn <- Optional() of String\n\n      aliceId <- 1\n      bobId <- 2\n\n      if userId == aliceId\n        rtn: Optional(\"Alice\")\n      else if userId == bobId\n        rtn: Optional(\"Bob\")\n\n    <?-\n      Simulates an address lookup that might not find a value.\n    -?>\n    findAddress() as pure\n      -> name as String\n      <- rtn <- Optional() of String\n\n      if name == \"Alice\"\n        rtn: Optional(\"123 Main Street\")\n\n  defines program\n\n    SwiftOptionalMigrationDemo()\n      stdout <- Stdout()\n\n      // === PATTERN 1: IF LET (SINGLE BINDING) ===\n      // Swift: if let user = findUser(1) { print(user) }\n\n      if user <- findUser(1)\n        stdout.println(`Found user: ${user.get()}`)\n\n      // === PATTERN 2: IF LET CHAIN (NESTED GUARDS) ===\n      // Swift: if let user = findUser(1), let addr = findAddress(user) { ... }\n\n      if user <- findUser(1)\n        if addr <- findAddress(user.get())\n          stdout.println(`${user.get()} lives at ${addr.get()}`)\n\n      // === PATTERN 3: GUARD LET BECOMES IF GUARD ===\n      // Swift: guard let user = findUser(3) else { return }\n      // EK9: success path inside the guard block\n\n      if user <- findUser(3)\n        stdout.println(`User 3: ${user.get()}`)\n      else\n        stdout.println(\"User 3 not found\")\n\n      // === PATTERN 4: NIL COALESCING ===\n      // Swift: let name = findUser(99) ?? \"Anonymous\"\n\n      lookup <- findUser(99)\n      userName <- lookup.getOrDefault(\"Anonymous\")\n      stdout.println(`User 99: ${userName}`)\n\n      // === NO FORCE UNWRAP ===\n      // Swift: let name = findUser(1)!  (crashes if nil)\n      // EK9: no equivalent. Must use guard or coalescing.","migrationContext":"Swift: if let for binding, guard let for early exit, ?? for nil coalescing, ! for force unwrap (crash risk), ?. for optional chaining. Kotlin: ?.let{} for binding, ?: for elvis, !! for force unwrap (NPE risk). Java: Optional.map().orElse() chains, .get() throws NoSuchElementException. Rust: if let Some(v) for binding, unwrap() for panic, ? operator for propagation. EK9: guard expressions (if x <- expr()) for binding, ?: for isSet coalescing, ?? for memory coalescing, no force unwrap, nested guards for chaining.","keywords":["binding","chain","coalescing","force","guard","let","migrate","nil","null-safe","optional","safe","swift","unwrap"],"primaryTopics":[],"typicalErrors":[{"error":"E50060","correct":"userName <- lookup.getOrDefault(\"Anonymous\")","incorrect":"userName <- lookup.unwrap()","explanation":"Optional has no unwrap() method. Use getOrDefault() for safe access with a fallback. EK9 has no force unwrap. See ek9 -h E50060 for details."},{"error":"E50060","correct":"if user <- findUser(1)\n        stdout.println(`Found user: ${user.get()}`)","incorrect":"if user <- findUser(1)\n        stdout.println(`Found user: ${user.getValue()}`)","explanation":"Optional has no getValue() method. Use get() inside a guard block. See ek9 -h E50060 for details."}],"companions":[]}
{"id":748,"category":"Control Flow","question":"How do I migrate Swift control flow patterns to EK9?","url":"https://ek9.io/qa/QA0748.html","alternatePhrasings":["What is the EK9 equivalent of Swift guard statement?","How do I translate Swift switch with where clauses to EK9?","How do I replace Swift for-where loops in EK9?","How do I convert Swift defer to EK9?"],"answer":"Swift developers coming to EK9 encounter five key control flow differences. Each has a direct EK9 equivalent.\n\nSWIFT GUARD STATEMENT\n  Swift:  guard let x = getValue() else { return nil }\n  EK9:    if x <- getValue()\n            processX(x)\nEK9 has no return statement. Instead of guard-else-return, put the success path inside the if block. For complex functions, decompose into smaller functions.\n\nSWIFT SWITCH WITH WHERE CLAUSES\n  Swift:  switch value {\n            case .high where temp > 100: alert()\n            case .low: monitor()\n          }\n  EK9:    switch level\n            case Level.High\n              if temp > threshold\n                alert()\n            case Level.Low\n              monitor()\nEK9 switch cases use nested if for additional conditions.\n\nSWIFT FOR-WHERE LOOPS\n  Swift:  for item in items where item.isValid {\n            process(item)\n          }\n  EK9:    cat items | filter by isValid > stdout\nOr with a for loop:\n  for item in items\n    if item.isValid()\n      process(item)\nSwift's for-where is a convenience. EK9 stream pipelines express filtering naturally.\n\nSWIFT DEFER\n  Swift:  func process() {\n            let file = open()\n            defer { file.close() }\n            use(file)\n          }\n  EK9:    try\n            -> resource <- openFile()\n            use(resource)\n          finally\n            cleanup()\nOr with try-with-resources for auto-close.\n\nSWIFT REPEAT-WHILE\n  Swift:  repeat { ... } while condition\n  EK9:    while condition\n            ...\nEK9 has no do-while loop. Use while with initialisation before the loop.\n\nSWIFT FALLTHROUGH\n  Swift:  switch x {\n            case 1: fallthrough\n            case 2: handle()\n          }\n  EK9:    switch x\n            case 1, 2\n              handle()\nEK9 has no fallthrough. Use comma-separated case values instead.\n\nSee Q144 for the full control flow philosophy. See Q145 for break and continue replacements. See Q146 for function decomposition. See Q147 for switch fallthrough replacements. See Q148 for Java and Python migration. See Q135 for try/finally. See Q137 for try-with-resources. See Q747 for Swift optional binding migration.","ek9Example":"defines module qa.controlflow.swiftmigration\n\n  defines type\n\n    Level\n      Low\n      Medium\n      High\n      Critical\n\n  defines function\n\n    isValid() as pure\n      -> item as String\n      <- rtn as Boolean: item?\n\n    <?-\n      Maps a level to its response action.\n    -?>\n    responseAction() as pure\n      -> level as Level\n      <- action as String: switch level\n        <- rtn as String: String()\n        case Level.Low\n          rtn: \"monitor\"\n        case Level.Medium\n          rtn: \"investigate\"\n        case Level.High, Level.Critical\n          rtn: \"escalate\"\n        default\n          rtn: \"unknown\"\n\n  defines program\n\n    SwiftControlFlowDemo()\n      stdout <- Stdout()\n\n      // === GUARD STATEMENT BECOMES IF GUARD ===\n      // Swift: guard let action = responseAction(.High) else { return }\n      // EK9: success path inside the if block\n\n      level <- Level.High\n      action <- responseAction(level)\n      if action?\n        stdout.println(`Action for ${level}: ${action}`)\n\n      // === SWITCH WITH CONDITIONS (replaces Swift where clause) ===\n\n      threshold <- Level.High\n      for lvl in Level\n        currentAction <- responseAction(lvl)\n        if lvl >= threshold\n          stdout.println(`ALERT: ${lvl} requires ${currentAction}`)\n        else\n          stdout.println(`OK: ${lvl} requires ${currentAction}`)\n\n      // === FOR-WHERE BECOMES STREAM PIPELINE ===\n      // Swift: for item in items where item.isValid { process(item) }\n\n      items <- [\"alpha\", String(), \"beta\", String(), \"gamma\"]\n      cat items | filter by isValid > stdout\n\n      // === COMMA-SEPARATED CASES (replaces Swift fallthrough) ===\n      // Swift: case .High: fallthrough; case .Critical: escalate()\n\n      testLevel <- Level.Critical\n      response <- switch testLevel\n        <- rtn as String: String()\n        case Level.Low, Level.Medium\n          rtn: \"routine\"\n        case Level.High, Level.Critical\n          rtn: \"emergency\"\n        default\n          rtn: \"unclassified\"\n      stdout.println(`${testLevel}: ${response}`)","migrationContext":"Swift: guard-else for early exit, switch with where clauses and pattern matching, for-where filtered loops, defer for cleanup, repeat-while for do-while, fallthrough keyword in switch. Java: break, continue, return, switch fallthrough. Python: break, continue, return, no switch until match 3.10. Rust: break, continue, implicit return, match with guards. EK9: guard expressions (if x <- expr()), nested if for switch conditions, stream pipelines for filtered iteration, try/finally for cleanup, comma-separated case values for fallthrough.","keywords":["control","defer","fallthrough","flow","guard","migrate","pattern","repeat","swift","switch","where","while"],"primaryTopics":[],"typicalErrors":[{"error":"E01072","correct":"if action?\n        stdout.println(`Action for ${level}: ${action}`)","incorrect":"if action?\n        stdout.println(`Action for ${level}: ${action}`)\n      return","explanation":"EK9 has no return statement. Use if guard expressions for success paths. See ek9 -h E01072 for details."},{"error":"E50060","correct":"action <- responseAction(level)","incorrect":"action <- responseAction(level).toString()","explanation":"String has no toString() method. responseAction() already returns a String. See ek9 -h E50060 for details."},{"error":"E50060","correct":"cat items | filter by isValid > stdout","incorrect":"cat items | filter by isValid > stdout.println()","explanation":"Stream output target must be a stream sink, not a method call. Use > stdout to pipe output directly. See ek9 -h E50060 for details."}],"companions":[]}
{"id":749,"category":"Fuzzing and Mutation Testing","question":"What is fuzzing and how does EK9 support it?","url":"https://ek9.io/qa/QA0749.html","alternatePhrasings":["Does EK9 have built-in fuzzing support?","What is the difference between fuzzing, mutation testing, and test generation in EK9?","How does EK9 fuzz testing compare to AFL or libFuzzer?"],"answer":"EK9 has three built-in fuzzing modes integrated directly into the compiler. No external tools, no separate configuration.\n\nTHREE MODES\n1. Compiler fuzzing (-fuzz): generates random EK9 source files and feeds them through the compiler to find parser and type-checker crashes. This tests the compiler itself.\n2. Mutation testing (-fuzzmutate): takes your source file, applies semantic mutations (swap operators, flip comparisons, remove guards), and checks whether your tests detect the change. Surviving mutants reveal weak tests.\n3. Test generation (-fuzztest): analyses your source, harvests types and functions, generates edge-case test programs, compiles them to verify correctness, and outputs the survivors as @Test programs.\n\nSOURCE-LEVEL AND TYPE-AWARE\nUnlike AFL or libFuzzer which operate on raw bytes, EK9 fuzzing understands the grammar and type system. Generated programs are syntactically valid EK9. Mutations respect operator semantics. Test generation uses type information to choose meaningful edge-case values.\n\nWHY BUILT-IN\nFuzzing as an external tool means most teams never use it. By integrating it into the compiler, EK9 makes fuzzing a normal part of the development workflow, not a specialist activity.\n\nSee Q750 for running the fuzzer. See Q753 for mutation testing. See Q754 for test generation. See Q758 for comparison with AFL and libFuzzer. See Q155 for writing tests. See Q157 for running tests.","ek9Example":"defines module qa.fuzzingandmutation.overview\n\n  <?-\n    Code that benefits from all three fuzzing modes.\n    The calculator operators would be flipped by mutation testing.\n    The boundary checks would be probed by test generation.\n    The parsing would be stressed by compiler fuzzing.\n  -?>\n\n  defines function\n\n    safeDiv() as pure\n      ->\n        numerator as Float\n        denominator as Float\n      <- result as Float: 0.0\n\n      zero <- 0.0\n      if denominator <> zero\n        result: numerator / denominator\n\n    clamp() as pure\n      ->\n        low as Float\n        high as Float\n        input as Float\n      <- result as Float: input\n\n      if input < low\n        result: low\n      else if input > high\n        result: high\n\n  defines program\n\n    FuzzingOverviewDemo()\n      stdout <- Stdout()\n\n      ten <- 10.0\n      three <- 3.0\n      stdout.println(`10 / 3 = ${safeDiv(ten, three)}`)\n\n      zero <- 0.0\n      stdout.println(`10 / 0 = ${safeDiv(ten, zero)}`)\n\n      low <- 0.0\n      high <- 100.0\n      belowMin <- -5.0\n      normal <- 50.0\n      aboveMax <- 150.0\n      stdout.println(`clamp(-5) = ${clamp(low, high, belowMin)}`)\n      stdout.println(`clamp(50) = ${clamp(low, high, normal)}`)\n      stdout.println(`clamp(150) = ${clamp(low, high, aboveMax)}`)","migrationContext":"Java: no built-in fuzzer, use Jazzer (external, byte-level). Python: no built-in fuzzer, use Atheris or Hypothesis (external). Rust: cargo-fuzz wraps libFuzzer (external, byte-level). Go: go test -fuzz (built-in since 1.18, byte-level). JavaScript: no built-in, use jsfuzz (external). EK9: three built-in modes (compiler fuzz, mutation testing, test generation), all source-level and type-aware, integrated into the compiler.","keywords":["AFL","built-in","compiler","fuzz","fuzzing","generation","libFuzzer","mutation","source-level","test","testing","type-aware"],"primaryTopics":["fuzzing","fuzz testing"],"typicalErrors":[],"companions":[]}
{"id":750,"category":"Fuzzing and Mutation Testing","question":"How do I run the EK9 fuzzer?","url":"https://ek9.io/qa/QA0750.html","alternatePhrasings":["What is the ek9 -fuzz flag for?","How do I fuzz the EK9 compiler to find crashes?","How long should I run the EK9 fuzzer?"],"answer":"The -fuzz flag runs grammar-based fuzz testing that stress-tests the compiler with randomly generated EK9 source files.\n\nBASIC USAGE\n  ek9 -fuzz 60\nThis runs the fuzzer for 60 minutes, generating random EK9 programs and feeding them through the full compilation pipeline. Crash-inducing inputs are saved to ./fuzz-crashes/ automatically.\n\nDURATION\nThe parameter is in minutes. Use longer durations for deeper coverage:\n  ek9 -fuzz 5      Quick smoke test (5 minutes).\n  ek9 -fuzz 60     Normal development session (1 hour).\n  ek9 -fuzz 1440   Overnight run (24 hours).\n\nWORKER THREADS\nThe fuzzer uses multiple worker threads to maximise throughput. It automatically detects available CPU cores and distributes work across them.\n\nWHAT IT GENERATES\nEach iteration creates a syntactically-aware EK9 source file containing randomly selected constructs: functions, classes, records, traits, enumerations, streams, control flow, operators, and generics. The generator understands EK9 grammar rules so that most files parse successfully and exercise deeper compiler phases.\n\nCRASH FILES\nWhen the compiler throws an unexpected exception on a generated file, that file is saved to ./fuzz-crashes/ with a descriptive filename. These files are minimal reproduction cases for bug reports.\n\nSee Q749 for fuzzing overview. See Q751 for output formats. See Q752 for the HTML dashboard. See Q757 for crash file details. See Q2 for compile and run basics.","ek9Example":"defines module qa.fuzzingandmutation.runfuzzer\n\n  <?-\n    Example code that a fuzzer might generate and test.\n    This demonstrates the kind of constructs the grammar-based\n    fuzzer combines: classes with operators, pure functions,\n    stream pipelines, and control flow.\n  -?>\n\n  defines class\n\n    Temperature\n      reading as Float: Float()\n\n      Temperature()\n        ->\n          initial as Float\n        this.reading :=? initial\n\n      operator $ as pure\n        <- rtn as String: $this.reading\n\n      operator < as pure\n        -> other as Temperature\n        <- rtn as Boolean: this.reading < other.reading\n\n      default operator ?\n\n  defines function\n\n    averageReading()\n      ->\n        readings as List of Float\n      <- average as Float: 0.0\n\n      count <- length readings\n      zero <- 0\n      if count > zero\n        total <- 0.0\n        for reading in readings\n          total: total + reading\n        average: total / #^count\n\n  defines program\n\n    RunFuzzerDemo()\n      stdout <- Stdout()\n\n      readings <- [20.5, 22.1, 19.8, 25.3, 18.7]\n      avg <- averageReading(readings)\n      stdout.println(`Average temperature: ${avg}`)\n\n      cold <- Temperature(15.0)\n      warm <- Temperature(30.0)\n      stdout.println(`Cold: ${cold}, Warm: ${warm}`)\n      coldLessThanWarm <- cold < warm\n      stdout.println(`Cold < Warm: ${coldLessThanWarm}`)","migrationContext":"Java: no built-in fuzzer, use Jazzer or JQF (separate tools, separate setup). Python: no built-in, Hypothesis for property-based testing, Atheris for coverage-guided fuzzing. Rust: cargo-fuzz wraps libFuzzer (must write harness functions). Go: go test -fuzz (built-in since 1.18 but requires writing Fuzz* functions). EK9: ek9 -fuzz <minutes> runs immediately with zero setup, no harness code needed.","keywords":["compiler","crash","duration","fuzz","generate","grammar","minutes","random","run","smoke","thread","worker"],"primaryTopics":[],"typicalErrors":[],"companions":[]}
{"id":751,"category":"Fuzzing and Mutation Testing","question":"What fuzz output formats are available in EK9?","url":"https://ek9.io/qa/QA0751.html","alternatePhrasings":["What is the difference between -fuzz, -fuzz0, -fuzz2, and -fuzz6?","How do I get JSON output from the EK9 fuzzer?","How do I integrate EK9 fuzzing into CI?"],"answer":"EK9 fuzzing supports four output formats, using the same suffix pattern as the test runner (-t flags).\n\nOUTPUT FORMAT FLAGS\n  ek9 -fuzz <minutes>    Human-readable output (default, same as -fuzz1).\n  ek9 -fuzz0 <minutes>   Terse output: one-line CI pass/fail summary.\n  ek9 -fuzz2 <minutes>   JSON output: fuzz-report.json + fuzz-snapshots.jsonl.\n  ek9 -fuzz6 <minutes>   HTML dashboard: fuzz-report/index.html.\n\nHUMAN-READABLE (-fuzz)\nShows a rolling summary with iterations per second, crash count, phase distribution, and error code histogram. Best for interactive development.\n\nTERSE (-fuzz0)\nOne-line output suitable for CI pipelines: total iterations, crashes found, duration. Exit code 0 if no crashes, non-zero otherwise.\n\nJSON (-fuzz2)\nStructured output for programmatic analysis:\n  fuzz-report.json: final summary with crash count, iterations, timing.\n  fuzz-snapshots.jsonl: periodic snapshots for throughput graphing.\nIdeal for AI-assisted analysis or custom dashboards.\n\nHTML DASHBOARD (-fuzz6)\nInteractive report at fuzz-report/index.html with:\n  Error code heatmap, phase distribution chart, throughput curve.\nSame visual quality as the -t6 coverage dashboard.\n\nCI INTEGRATION\nFor CI, use -fuzz0 with a short duration as a smoke test:\n  ek9 -fuzz0 5\nCheck the exit code and fail the build if crashes are found.\n\nSee Q749 for fuzzing overview. See Q750 for running the fuzzer. See Q752 for reading the HTML dashboard. See Q157 for test output formats (same suffix pattern).","ek9Example":"defines module qa.fuzzingandmutation.outputformats\n\n  <?-\n    Demonstrates code with multiple output paths that would\n    produce different fuzz statistics depending on coverage.\n    The output format flags control how those statistics\n    are reported, not what the code does.\n  -?>\n\n  defines function\n\n    categorise() as pure\n      -> score as Integer\n      <- category as String: \"unknown\"\n\n      failThreshold <- 40\n      passThreshold <- 70\n      excellentThreshold <- 90\n\n      if score < failThreshold\n        category: \"fail\"\n      else if score < passThreshold\n        category: \"pass\"\n      else if score < excellentThreshold\n        category: \"good\"\n      else\n        category: \"excellent\"\n\n    formatResult() as pure\n      ->\n        studentName as String\n        score as Integer\n      <- report as String: String()\n\n      grade <- categorise(score)\n      report: `${studentName}: ${score} (${grade})`\n\n  defines program\n\n    FuzzOutputFormatsDemo()\n      stdout <- Stdout()\n\n      scores <- [35, 55, 78, 95]\n      names <- [\"Alice\", \"Bob\", \"Carol\", \"Dave\"]\n\n      idx <- 0\n      defaultName <- \"Unknown\"\n      for score in scores\n        studentName <- names.getOrDefault(idx, defaultName)\n        if studentName?\n          stdout.println(formatResult(studentName, score))\n        idx: idx + 1","migrationContext":"Java: Jazzer outputs to stdout or JUnit XML (limited formats). Python: Atheris writes crash files, no structured output. Rust: cargo-fuzz outputs crash files and stdout only. Go: go test -fuzz writes to testdata/ directory. EK9: four built-in output formats (human, terse, JSON, HTML) matching the test runner pattern, with CI-friendly exit codes.","keywords":["CI","dashboard","format","fuzz","fuzz0","fuzz2","fuzz6","html","json","output","report","terse"],"primaryTopics":[],"typicalErrors":[],"companions":[]}
{"id":752,"category":"Fuzzing and Mutation Testing","question":"How do I read the EK9 fuzz HTML dashboard?","url":"https://ek9.io/qa/QA0752.html","alternatePhrasings":["What does the fuzz-report/index.html show?","How do I interpret EK9 fuzz statistics?","What are error code heatmaps in the fuzz report?"],"answer":"The -fuzz6 flag generates an interactive HTML dashboard at fuzz-report/index.html with four key sections.\n\nERROR CODE HEATMAP\nA colour-coded matrix of every compiler error code triggered during fuzzing. Brighter cells mean more frequent triggers. Error codes that never appear reveal gaps in the fuzzer's grammar coverage.\n\nPHASE DISTRIBUTION\nA bar chart showing how many generated files reached each compilation phase (PARSING, SYMBOL_DEFINITION, TYPE_HIERARCHY_CHECKS, PRE_IR_CHECKS, etc.). A healthy fuzzer produces files that exercise all phases, not just the parser.\n\nTHROUGHPUT CURVE\nA time-series graph of iterations per second. Drops in throughput indicate complex generated files that take longer to compile. Sustained low throughput may reveal compiler performance issues.\n\nCRASH SUMMARY\nA table listing each unique crash with: error message, stack trace hash, affected phase, and link to the minimal reproduction file in ./fuzz-crashes/.\n\nREADING THE DASHBOARD\n- All error codes triggered = good fuzzer coverage.\n- Files reaching deep phases = grammar generator working well.\n- Zero crashes after long runs = compiler is robust.\n- Surviving error code gaps = improve generator for those constructs.\n\nSee Q749 for fuzzing overview. See Q751 for output formats. See Q627 for profiling dashboard (similar HTML pattern). See Q321 for quality report dashboard.","ek9Example":"defines module qa.fuzzingandmutation.htmldashboard\n\n  <?-\n    Code with multiple error paths that produce\n    a rich distribution in the fuzz heatmap.\n    Different branches exercise different compiler\n    phases and error detection logic.\n  -?>\n\n  defines trait\n\n    Measurable\n      measurement() as pure\n        <- rtn as Float: 0.0\n\n      label() as pure\n        <- rtn as String: String()\n\n  defines class\n\n    Sensor as open\n      sensorName as String: String()\n      currentReading as Float: 0.0\n\n      Sensor()\n        ->\n          initialName as String\n        this.sensorName :=? initialName\n\n      reading() as pure\n        <- rtn as Float: this.currentReading\n\n      updateReading()\n        -> newReading as Float\n        this.currentReading: newReading\n\n      operator $ as pure\n        <- rtn as String: `${this.sensorName}: ${this.currentReading}`\n\n      default operator ?\n\n  defines function\n\n    summariseSensors()\n      -> sensors as List of Sensor\n      <- summary as String: \"No sensors\"\n\n      count <- length sensors\n      zero <- 0\n      if count > zero\n        total <- 0.0\n        for sensor in sensors\n          total: total + sensor.reading()\n        avg <- total / #^count\n        summary: `${count} sensors, avg ${avg}`\n\n  defines program\n\n    FuzzHtmlDashboardDemo()\n      stdout <- Stdout()\n\n      alpha <- Sensor(\"alpha\")\n      beta <- Sensor(\"beta\")\n      gamma <- Sensor(\"gamma\")\n\n      alpha.updateReading(23.5)\n      beta.updateReading(19.8)\n      gamma.updateReading(27.1)\n\n      sensors <- [alpha, beta, gamma]\n      for sensor in sensors\n        stdout.println($sensor)\n\n      stdout.println(summariseSensors(sensors))","migrationContext":"Java: Jazzer provides basic crash reports, no HTML dashboard. Python: Atheris writes crash files only, visualisation requires custom tools. Rust: cargo-fuzz has no built-in dashboard. Go: go test -fuzz writes to testdata, no visualisation. EK9: built-in interactive HTML dashboard with heatmaps, charts, and crash details generated by -fuzz6.","keywords":["code","crash","dashboard","error","fuzz","heatmap","html","phase","report","statistics","throughput","visualisation"],"primaryTopics":[],"typicalErrors":[],"companions":[]}
{"id":753,"category":"Fuzzing and Mutation Testing","question":"How does mutation testing work in EK9?","url":"https://ek9.io/qa/QA0753.html","alternatePhrasings":["What is the ek9 -fuzzmutate flag for?","How do I assess test quality with mutation testing in EK9?","What mutation categories does EK9 apply?"],"answer":"Mutation testing measures the quality of your tests by introducing small, deliberate bugs into your source code and checking whether your tests detect them.\n\nBASIC USAGE\n  ek9 -fuzzmutate source.ek9 mutations/\nThis analyses source.ek9, generates mutated variants, and writes them to the mutations/ directory. Each variant has exactly one semantic change that compiles but alters behaviour.\n\nMUTATION CATEGORIES\nThe mutator applies these categories of change:\n  ARITH_SWAP: + becomes -, * becomes / (and vice versa).\n  CMP_FLIP: < becomes >=, == becomes <>.\n  GUARD_STRIP: removes if-guard conditions (if x <- expr becomes unconditional).\n  CONST_BUMP: changes literal values (0 becomes 1, boundary shifts).\n  NEGATE_BOOL: flips Boolean expressions (true becomes false).\n  SWAP_LINES: reorders independent statements.\n  DUP_OPERAND: duplicates an operand (x + y becomes x + x).\n  REMOVE_CALL: removes a method call statement.\n\nMANIFEST FILE\nThe mutations/ directory contains a manifest.txt listing each variant with its mutation category, location, and description. This file is machine-readable for CI integration.\n\nKILL CHECK\nRun your tests against each mutant to check detection:\n  ek9 -fuzzmutate source.ek9 mutations/ -test tests.ek9\nMutants that survive (tests still pass) reveal gaps in your test suite.\n\nOPTIONS\n  -n 50          Generate at most 50 mutants (default 100).\n  -seed 42       Reproducible mutation selection.\n  -overwrite     Replace existing mutations/ directory.\n\nSee Q749 for fuzzing overview. See Q754 for test generation. See Q756 for the quality loop. See Q155 for writing tests. See Q206 for test coverage.","ek9Example":"defines module qa.fuzzingandmutation.mutationtesting\n\n  <?-\n    Code designed to show how mutation testing reveals weak tests.\n    Each operator and comparison is a mutation target.\n    If tests do not check boundary conditions, mutants survive.\n  -?>\n\n  defines function\n\n    <?-\n      A mutation tester would flip < to >=, changing the behaviour.\n      If your test only checks the happy path, the mutant survives.\n    -?>\n    isWithinRange() as pure\n      ->\n        low as Integer\n        high as Integer\n        candidate as Integer\n      <- inRange as Boolean: false\n\n      if candidate >= low and candidate <= high\n        inRange: true\n\n    <?-\n      ARITH_SWAP would change + to - in the discount calculation.\n      CMP_FLIP would change > to <= in the threshold check.\n    -?>\n    applyDiscount() as pure\n      ->\n        price as Float\n        discountRate as Float\n      <- finalPrice as Float: price\n\n      minimumRate <- 0.0\n      maximumRate <- 1.0\n      if discountRate > minimumRate and discountRate <= maximumRate\n        reduction <- price * discountRate\n        finalPrice: price - reduction\n\n  defines program\n\n    MutationTestingDemo()\n      stdout <- Stdout()\n\n      low <- 1\n      high <- 10\n      for candidate in [0, 1, 5, 10, 11]\n        result <- isWithinRange(low, high, candidate)\n        stdout.println(`${candidate} in [${low}..${high}]: ${result}`)\n\n      price <- 100.0\n      rate <- 0.25\n      discounted <- applyDiscount(price, rate)\n      stdout.println(`Price ${price} at ${rate} discount: ${discounted}`)","migrationContext":"Java: PIT (Pitest) for mutation testing, separate Maven/Gradle plugin, slow on large codebases. Python: mutmut or cosmic-ray (external tools). Rust: no mainstream mutation testing tool. Go: no built-in, experimental tools like go-mutesting. EK9: built-in ek9 -fuzzmutate with manifest output, kill-check via -test flag, and reproducible seeds.","keywords":["flip","fuzzmutate","guard","kill","mutant","mutate","mutation","quality","strip","survive","swap","test","testing"],"primaryTopics":["mutation testing","mutation test"],"typicalErrors":[],"companions":[]}
{"id":754,"category":"Fuzzing and Mutation Testing","question":"How do I generate test cases with -fuzztest in EK9?","url":"https://ek9.io/qa/QA0754.html","alternatePhrasings":["What is the ek9 -fuzztest flag for?","How does EK9 automatically generate unit tests?","How do I use EK9 test generation for edge cases?"],"answer":"The -fuzztest flag analyses your source code and automatically generates edge-case @Test programs.\n\nBASIC USAGE\n  ek9 -fuzztest source.ek9 output.ek9\nThis reads source.ek9, harvests all functions and types, generates candidate test programs, compiles each to verify correctness, and writes the survivors to output.ek9.\n\nSYMBOL HARVESTING\nThe generator inspects your source to discover:\n  Functions: parameter types, return types, and purity.\n  Classes: constructors, operators, and public methods.\n  Records: constructors and operators.\n  Enumerations: values and constrained ranges.\n\nEDGE-CASE VALUES\nFor each parameter type, the generator selects meaningful edge-case values:\n  Integer: 0, 1, -1, MAX, MIN, boundary values.\n  Float: 0.0, -0.0, very small, very large, boundary values.\n  String: empty, single char, very long, special characters.\n  Boolean: true, false.\n  Collections: empty, single item, many items.\n\nCOMPILE-CHECK-DISCARD\nEach generated test candidate is compiled through PRE_IR_CHECKS. If it fails to compile (type error, missing operator, invalid combination), it is silently discarded. Only compilable, type-correct tests survive into the output file.\n\nOPTIONS\n  -n 200          Generate at most 200 candidates (default 200).\n  -seed 42        Reproducible test generation.\n  -overwrite      Replace existing output file.\n\nOUTPUT FORMAT\nThe output file is a compilable EK9 module containing @Test programs. Each test calls one function or method with edge-case arguments and prints the result for manual review.\n\nSee Q749 for fuzzing overview. See Q753 for mutation testing. See Q755 for compile-check-discard philosophy. See Q155 for writing tests. See Q157 for running tests.","ek9Example":"defines module qa.fuzzingandmutation.generatetests\n\n  <?-\n    Source code that would be analysed by -fuzztest.\n    The generator would harvest these functions and create\n    @Test programs exercising edge-case inputs.\n  -?>\n\n  defines function\n\n    <?-\n      Test generator would try: 0, 1, -1, MAX, MIN.\n      Edge case: negative numbers should return false.\n    -?>\n    isPrime()\n      -> candidate as Integer\n      <- prime as Boolean: false\n\n      two <- 2\n      if candidate >= two\n        divisor <- two\n        foundDivisor <- false\n        while divisor * divisor <= candidate and not foundDivisor\n          zero <- 0\n          if candidate mod divisor == zero\n            foundDivisor: true\n          divisor: divisor + 1\n        prime: not foundDivisor\n\n    <?-\n      Test generator would try: empty string, single char,\n      already uppercase, mixed case.\n    -?>\n    isAllUpperCase()\n      -> text as String\n      <- allUpper as Boolean: false\n\n      if text?\n        upper <- text.upperCase()\n        allUpper: text == upper\n\n  defines program\n\n    GenerateTestsDemo()\n      stdout <- Stdout()\n\n      for candidate in [1, 2, 3, 4, 5, 17, 20, 97]\n        stdout.println(`${candidate} prime: ${isPrime(candidate)}`)\n\n      for word in [\"HELLO\", \"Hello\", \"world\", \"EK9\"]\n        stdout.println(`${word} all upper: ${isAllUpperCase(word)}`)","migrationContext":"Java: no built-in test generation, use EvoSuite or Randoop (external, bytecode-level). Python: Hypothesis generates property-based tests (requires writing strategies). Rust: proptest for property-based testing (requires writing arbitraries). Go: go test -fuzz generates inputs but not full test functions. EK9: ek9 -fuzztest generates complete @Test programs from source analysis, compile-checked for correctness.","keywords":["boundary","candidate","case","check","compile","discard","edge","fuzztest","generate","generation","harvest","symbol","test"],"primaryTopics":[],"typicalErrors":[],"companions":[]}
{"id":755,"category":"Fuzzing and Mutation Testing","question":"Why does EK9 use compile-check-discard for generated tests?","url":"https://ek9.io/qa/QA0755.html","alternatePhrasings":["How does EK9 ensure generated tests are valid?","What is the compile-check-discard pattern in EK9 test generation?","Why does EK9 compile generated tests before outputting them?"],"answer":"EK9 test generation uses a compile-check-discard pattern: generate a candidate test, compile it, keep it only if compilation succeeds. This is fundamentally different from every other test generation tool.\n\nTHE COMPILER IS THE FILTER\nOther tools generate tests and hope they are valid. EK9 generates candidates and uses its own compiler as an oracle. A candidate that fails type checking, violates purity, uses an invalid operator, or triggers any quality check is silently discarded. Only programs that pass ALL compiler checks survive.\n\nWHY THIS WORKS IN EK9\nEK9 has no warnings. Every check is pass or fail. This means the compiler gives a definitive answer: this candidate is either fully valid or it is not. There is no ambiguity, no 'valid with warnings' gray area.\n\nTYPE-AWARE GENERATION\nBecause the generator knows the type system, it generates calls with correct types. But edge-case combinations (empty string to a function expecting non-empty, zero to a divider) may fail purity or data-flow checks. Compile-check-discard handles this automatically.\n\nPRACTICAL BENEFIT\nThe output file contains ONLY tests that:\n  1. Parse correctly (valid EK9 syntax).\n  2. Pass type checking (correct argument types).\n  3. Pass data flow analysis (no uninitialised variables).\n  4. Pass quality checks (no magic literals, no dead code).\nThis is a guarantee no other test generator provides.\n\nNO OTHER LANGUAGE DOES THIS\nAFL, libFuzzer, EvoSuite, Hypothesis, and cargo-fuzz all generate inputs, not full programs. EK9 generates complete @Test programs and validates them against the full compiler pipeline.\n\nSee Q749 for fuzzing overview. See Q754 for test generation. See Q753 for mutation testing. See Q310 for quality at compile time.","ek9Example":"defines module qa.fuzzingandmutation.compilecheckdiscard\n\n  <?-\n    Demonstrates functions whose edge-case combinations\n    could fail quality checks. The compile-check-discard\n    pattern automatically handles these cases.\n  -?>\n\n  defines function\n\n    <?-\n      Test generator might try dividing by zero.\n      The guard prevents a crash, but a candidate test\n      calling this with zero would still compile because\n      the guard handles it. Compile-check-discard keeps it.\n    -?>\n    safeDivide() as pure\n      ->\n        numerator as Integer\n        divisor as Integer\n      <- result as Integer: 0\n\n      zero <- 0\n      if divisor <> zero\n        result: numerator / divisor\n\n    <?-\n      Test generator might try empty strings.\n      The guard prevents accessing an empty string.\n      Generated tests with empty input compile and exercise the guard.\n    -?>\n    safeFirstChar() as pure\n      -> text as String\n      <- found as Character: Character()\n\n      if text?\n        found :=? text.first()\n\n    validateAge() as pure\n      -> age as Integer\n      <- valid as Boolean: false\n\n      minimumAge <- 0\n      maximumAge <- 150\n      if age >= minimumAge and age <= maximumAge\n        valid: true\n\n  defines program\n\n    CompileCheckDiscardDemo()\n      stdout <- Stdout()\n\n      ten <- 10\n      three <- 3\n      zero <- 0\n      stdout.println(`10 / 3 = ${safeDivide(ten, three)}`)\n      stdout.println(`10 / 0 = ${safeDivide(ten, zero)}`)\n\n      greeting <- \"Hello\"\n      emptyText <- String()\n      stdout.println(`first of Hello: ${safeFirstChar(greeting)}`)\n      stdout.println(`first of empty: ${safeFirstChar(emptyText)}`)\n\n      for age in [0, 25, 150, -1, 200]\n        stdout.println(`age ${age} valid: ${validateAge(age)}`)","migrationContext":"Java: EvoSuite generates tests that may not compile or may have warnings. Python: Hypothesis generates inputs, not full test programs. Rust: proptest generates values, not complete test functions. Go: go test -fuzz generates corpus entries, not test functions. EK9: generates complete @Test programs validated by the full compiler pipeline, discarding any that fail any check.","keywords":["aware","check","compile","discard","filter","generation","guarantee","oracle","quality","test","type","valid"],"primaryTopics":[],"typicalErrors":[],"companions":[]}
{"id":756,"category":"Fuzzing and Mutation Testing","question":"How does fuzzing relate to code coverage and quality in EK9?","url":"https://ek9.io/qa/QA0756.html","alternatePhrasings":["What is the EK9 quality loop for testing?","How do I combine fuzzing, mutation testing, and coverage in EK9?","How do I improve test quality in EK9 systematically?"],"answer":"EK9 provides a complete quality loop: write tests, measure coverage, generate more tests, mutate to assess quality, and fuzz for robustness.\n\nTHE QUALITY LOOP (5 STEPS)\n1. Write tests: create @Test programs for your functions.\n  ek9 -t source.ek9\n2. Check coverage: identify untested code.\n  ek9 -tC source.ek9\n  Coverage must reach 80% to package with -P.\n3. Generate tests: fill coverage gaps automatically.\n  ek9 -fuzztest source.ek9 generated.ek9\n  Review generated tests, keep the useful ones.\n4. Mutation testing: assess test quality.\n  ek9 -fuzzmutate source.ek9 mutations/ -test tests.ek9\n  Surviving mutants show where tests are too weak.\n5. Compiler fuzzing: verify robustness.\n  ek9 -fuzz 60\n  Ensures the compiler handles edge cases in your patterns.\n\nCOVERAGE AND MUTATION TOGETHER\nCoverage tells you what code is executed by tests. Mutation testing tells you whether tests actually verify behaviour. 100% coverage with weak assertions is meaningless. Mutation testing reveals this gap.\n\nAI-ASSISTED WORKFLOW\nUse -t2 (JSON test output) and -fuzz2 (JSON fuzz output) to feed results into AI analysis. The AI can identify patterns in surviving mutants and suggest targeted tests.\n\nCONTINUOUS IMPROVEMENT\nRun the quality loop regularly:\n  Development: write tests, check coverage.\n  Pre-commit: mutation testing on changed files.\n  CI: short fuzz run as smoke test.\n  Nightly: long fuzz run for deep coverage.\n\nSee Q749 for fuzzing overview. See Q753 for mutation testing. See Q754 for test generation. See Q155 for writing tests. See Q206 for test coverage. See Q310 for quality at compile time.","ek9Example":"defines module qa.fuzzingandmutation.qualityloop\n\n  <?-\n    A small library with tests that demonstrate the quality loop.\n    Step 1: Write tests. Step 2: Check coverage.\n    Step 3: Generate more. Step 4: Mutate. Step 5: Fuzz.\n  -?>\n\n  defines function\n\n    maximum() as pure\n      ->\n        first as Integer\n        second as Integer\n      <- result as Integer: first\n\n      if second > first\n        result: second\n\n    minimum() as pure\n      ->\n        first as Integer\n        second as Integer\n      <- result as Integer: first\n\n      if second < first\n        result: second\n\n    absoluteValue() as pure\n      -> number as Integer\n      <- result as Integer: number\n\n      zero <- 0\n      if number < zero\n        result: 0 - number\n\n  defines program\n\n    // === STEP 1: Write basic tests ===\n\n    @Test\n    MaximumTest()\n      three <- 3\n      seven <- 7\n      assert maximum(three, seven) == 7\n      assert maximum(seven, three) == 7\n\n    @Test\n    MinimumTest()\n      three <- 3\n      seven <- 7\n      assert minimum(three, seven) == 3\n      assert minimum(seven, three) == 3\n\n    @Test\n    AbsoluteValueTest()\n      five <- 5\n      negativeFive <- -5\n      zero <- 0\n      assert absoluteValue(five) == 5\n      assert absoluteValue(negativeFive) == 5\n      assert absoluteValue(zero) == 0\n\n    // === Demo program showing the functions ===\n\n    QualityLoopDemo()\n      stdout <- Stdout()\n\n      three <- 3\n      seven <- 7\n      stdout.println(`max(3, 7) = ${maximum(three, seven)}`)\n      stdout.println(`min(3, 7) = ${minimum(three, seven)}`)\n      stdout.println(`abs(-5) = ${absoluteValue(-5)}`)","migrationContext":"Java: requires separate tools for each step (JUnit, JaCoCo, PIT, Jazzer) with different configs. Python: pytest + pytest-cov + mutmut + Atheris (all separate installs). Rust: cargo-test + tarpaulin + cargo-fuzz (no mutation tool). Go: go test + go test -cover + go test -fuzz (no mutation tool). EK9: complete quality loop in one tool: -t for tests, -tC for coverage, -fuzztest for generation, -fuzzmutate for mutation, -fuzz for robustness.","keywords":["CI","continuous","coverage","fuzz","generation","improve","loop","mutation","quality","systematic","test","workflow"],"primaryTopics":[],"typicalErrors":[],"companions":[]}
{"id":757,"category":"Fuzzing and Mutation Testing","question":"Where do crash files and generated tests go in EK9?","url":"https://ek9.io/qa/QA0757.html","alternatePhrasings":["What is the ./fuzz-crashes/ directory?","How do I manage EK9 fuzzer output files?","What options control EK9 fuzz output locations?"],"answer":"EK9 fuzzing tools write output to predictable locations with options to control overwriting and reproducibility.\n\nCRASH FILES (-fuzz)\nLocation: ./fuzz-crashes/\nWhen the compiler crashes on a generated file, the minimal reproduction case is saved here. Each file has a descriptive name including the phase that crashed and a hash of the stack trace.\n\nGENERATED TESTS (-fuzztest)\n  ek9 -fuzztest source.ek9 output.ek9\nThe second argument is the output file path. By default, the command errors if the output file already exists. Use -overwrite to replace it.\n\nMUTATION VARIANTS (-fuzzmutate)\n  ek9 -fuzzmutate source.ek9 mutations/\nThe second argument is the output directory. Contains:\n  manifest.txt: lists each variant with mutation category and location.\n  variant_001.ek9, variant_002.ek9, ...: individual mutated files.\nBy default, errors if the directory already exists. Use -overwrite to replace.\n\nCONTROLLING OUTPUT\n  -overwrite     Allow replacing existing output file or directory.\n  -seed <n>      Reproducible generation (same seed produces same output).\n  -n <n>         Maximum number of candidates (-fuzztest default 200, -fuzzmutate default 100).\n\nCI WORKFLOW\nIn CI, use -overwrite so that each build produces fresh output:\n  ek9 -fuzztest source.ek9 generated-tests.ek9 -overwrite\n  ek9 -fuzzmutate source.ek9 mutations/ -overwrite -test tests.ek9\n\nSee Q749 for fuzzing overview. See Q750 for running the fuzzer. See Q753 for mutation testing. See Q754 for test generation. See Q2 for compile and run.","ek9Example":"defines module qa.fuzzingandmutation.crashfiles\n\n  <?-\n    Demonstrates code patterns that might produce crash\n    files when fuzzed, and test output when generated.\n    The output management options ensure CI repeatability.\n  -?>\n\n  defines class\n\n    Stack\n      items as List of String: List() of String\n\n      push()\n        -> item as String\n        this.items += item\n\n      pop()\n        <- popped as String: String()\n\n        count <- length this.items\n        zero <- 0\n        emptyDefault <- String()\n        if count > zero\n          lastIndex <- count - 1\n          topItem <- this.items.getOrDefault(lastIndex, emptyDefault)\n          if topItem?\n            popped: topItem\n            this.items -= topItem\n\n      peek() as pure\n        <- topItem as String: String()\n\n        count <- length this.items\n        zero <- 0\n        emptyDefault <- String()\n        if count > zero\n          lastIndex <- count - 1\n          topItem :=? this.items.getOrDefault(lastIndex, emptyDefault)\n\n      size() as pure\n        <- count as Integer: length this.items\n\n      operator $ as pure\n        <- rtn as String: $this.items\n\n      default operator ?\n\n  defines program\n\n    CrashFilesDemo()\n      stdout <- Stdout()\n\n      stack <- Stack()\n      stack.push(\"first\")\n      stack.push(\"second\")\n      stack.push(\"third\")\n      stdout.println(`Stack: ${stack}`)\n\n      popped <- stack.pop()\n      stdout.println(`Popped: ${popped}`)\n      stdout.println(`Peek: ${stack.peek()}`)\n      stdout.println(`Size: ${stack.size()}`)","migrationContext":"Java: Jazzer saves crashes to a corpus directory, no structured manifest. Python: Atheris writes to crash-* files in current directory. Rust: cargo-fuzz saves to fuzz/artifacts/<target>/. Go: go test -fuzz saves to testdata/fuzz/<FuzzTestName>/. EK9: crash files in ./fuzz-crashes/, generated tests to specified file, mutations to specified directory with manifest.txt, all controllable via -overwrite, -seed, -n.","keywords":["crash","directory","files","fuzz-crashes","location","manifest","mutation","output","overwrite","reproducible","seed","variant"],"primaryTopics":[],"typicalErrors":[],"companions":[]}
{"id":758,"category":"Fuzzing and Mutation Testing","question":"How is EK9 fuzzing different from AFL or libFuzzer?","url":"https://ek9.io/qa/QA0758.html","alternatePhrasings":["Why is EK9 fuzzing source-level instead of byte-level?","How does EK9 fuzzing compare to coverage-guided fuzzing?","What advantages does type-aware fuzzing give EK9?"],"answer":"EK9 fuzzing differs from traditional fuzzers in three fundamental ways: it operates at source level, understands types, and provides three modes instead of one.\n\nSOURCE-LEVEL VS BYTE-LEVEL\nAFL and libFuzzer mutate raw bytes and feed them to a compiled binary. Most mutated inputs are immediately rejected as invalid. EK9 generates valid source programs from the grammar, so every generated file is syntactically correct and exercises deeper compiler phases.\n\nTYPE-AWARE VS TYPE-BLIND\nAFL has no concept of types. It might flip a bit in a pointer, producing a segfault rather than testing logic. EK9 understands Integer, Float, String, and custom types. Generated programs use correct types, operators, and calling conventions.\n\nTHREE MODES VS ONE\nAFL does one thing: coverage-guided binary fuzzing. EK9 provides:\n  1. Compiler fuzzing (-fuzz): stress-test the compiler itself.\n  2. Mutation testing (-fuzzmutate): assess test quality.\n  3. Test generation (-fuzztest): create edge-case tests.\nThese three modes cover different quality dimensions.\n\nNO HARNESS REQUIRED\nAFL and libFuzzer require writing a harness function that accepts byte input and converts it to the target format. EK9 fuzzing works on source files directly with zero setup.\n\nCOVERAGE-GUIDED VS GRAMMAR-GUIDED\nAFL uses code coverage feedback to guide mutations toward new execution paths. EK9 uses grammar rules and type information to generate structurally diverse programs. Both approaches find different kinds of bugs.\n\nWHEN TO USE WHICH\n  EK9 fuzzing: for EK9 projects, always use the built-in tools.\n  AFL/libFuzzer: for C/C++ libraries or when testing binary interfaces.\n  Both: complementary approaches for different layers of the stack.\n\nSee Q749 for fuzzing overview. See Q750 for running the fuzzer. See Q753 for mutation testing. See Q754 for test generation.","ek9Example":"defines module qa.fuzzingandmutation.fuzzvsafl\n\n  <?-\n    Demonstrates type-aware code that a byte-level fuzzer\n    would struggle with but EK9 source-level fuzzing handles\n    naturally. The type system ensures generated inputs are meaningful.\n  -?>\n\n  defines type\n\n    Priority\n      Low\n      Normal\n      High\n      Critical\n\n  defines class\n\n    Task\n      taskName as String: String()\n      taskPriority as Priority: Priority()\n      completed as Boolean: false\n\n      Task()\n        ->\n          initialName as String\n          initialPriority as Priority\n        this.taskName :=? initialName\n        this.taskPriority :=? initialPriority\n\n      complete()\n        this.completed: true\n\n      isComplete() as pure\n        <- done as Boolean: this.completed\n\n      priority() as pure\n        <- rtn as Priority: this.taskPriority\n\n      operator $ as pure\n        <- rtn as String: `${this.taskName} [${this.taskPriority}]`\n\n      default operator ?\n\n  defines function\n\n    countByPriority() as pure\n      ->\n        tasks as List of Task\n        targetPriority as Priority\n      <- count as Integer: 0\n\n      for task in tasks\n        if task.priority() == targetPriority\n          count: count + 1\n\n  defines program\n\n    FuzzVsAflDemo()\n      stdout <- Stdout()\n\n      tasks <- List() of Task\n      tasks += Task(\"Write docs\", Priority.Low)\n      tasks += Task(\"Fix login\", Priority.Critical)\n      tasks += Task(\"Add tests\", Priority.High)\n      tasks += Task(\"Update deps\", Priority.Normal)\n      tasks += Task(\"Review PR\", Priority.High)\n\n      for task in tasks\n        stdout.println($task)\n\n      highCount <- countByPriority(tasks, Priority.High)\n      criticalCount <- countByPriority(tasks, Priority.Critical)\n      stdout.println(`High priority: ${highCount}`)\n      stdout.println(`Critical: ${criticalCount}`)","migrationContext":"Java: Jazzer (coverage-guided, byte-level, requires harness). Python: Atheris (coverage-guided via libFuzzer, byte-level, requires harness). Rust: cargo-fuzz (coverage-guided via libFuzzer, byte-level, requires harness). Go: go test -fuzz (coverage-guided, byte-level, requires Fuzz* functions). AFL/libFuzzer: coverage-guided, byte-level, external tools. EK9: grammar-guided, source-level, type-aware, three modes, zero harness, built-in.","keywords":["AFL","byte-level","comparison","coverage","grammar","guided","harness","libFuzzer","modes","source-level","three","type-aware"],"primaryTopics":[],"typicalErrors":[],"companions":[]}
{"id":759,"category":"Control Flow","question":"How do I use a guard expression as a standalone assignment that returns Boolean?","url":"https://ek9.io/qa/QA0759.html","alternatePhrasings":["Can I use ?= outside of an if or while statement?","How does the guard expression ?= return a Boolean result?","What does guardResult <- name ?= getValue() do in EK9?"],"answer":"The guard expression operator (?=) can be used as a standalone assignment expression that returns a Boolean. The expression evaluates the right-hand side (RHS), checks if it is SET, and if so assigns it to the target variable. The Boolean result indicates whether the assignment occurred.\n\nSTANDALONE GUARD EXPRESSION\nUnlike using ?= in control flow guards (if, while), this pattern captures the Boolean result:\n  name as String?\n  guardResult <- name ?= getValue()\nHere guardResult is a Boolean: true if getValue() returned a set value (and name was assigned), false otherwise.\n\nWHEN THE RHS IS SET\nIf getValue() returns a set String, the assignment happens and the result is true:\n  name as String?\n  result <- name ?= getValue()\n  // result is true, name is now the returned value\n\nWHEN THE RHS IS UNSET\nIf the RHS returns an unset value, no assignment occurs and the result is false:\n  existing <- \"already here\"\n  result <- existing ?= getEmpty()\n  // result is false, existing is unchanged\n\nKEY DIFFERENCE FROM :=? AND ?= IN GUARDS\nThe three related patterns:\n  name :=?  checks TARGET isSet, assigns if unset\n  if name ?=  checks SOURCE isSet, controls flow\n  result <- name ?=  checks SOURCE isSet, returns Boolean\nThe standalone form returns Boolean rather than controlling a block.\n\nSee Q74 for guard variables in if. See Q77 for guard variables in while. See Q79 for guarded assignment :=? operator.","ek9Example":"defines module qa.flow.guard.expression.boolean\n\n  defines function\n\n    getValue()\n      <- rtn <- String()\n      rtn: \"hello\"\n\n    getEmpty()\n      <- rtn <- String()\n\n  defines program\n\n    GuardExpressionBooleanDemo()\n      stdout <- Stdout()\n\n      // === GUARD EXPRESSION WITH SET RHS ===\n\n      name as String?\n      guardResult <- name ?= getValue()\n      stdout.println(guardResult)\n      stdout.println(name)\n\n      // === GUARD EXPRESSION WITH UNSET RHS ===\n\n      existing <- \"already here\"\n      guardResult2 <- existing ?= getEmpty()\n      stdout.println(guardResult2)\n      stdout.println(existing)\n\n      stdout.println(\"Done\")","migrationContext":"Java: No direct equivalent. Must write: 'Optional<String> opt = getValue(); boolean assigned = opt.isPresent(); if (assigned) name = opt.get();'. Three separate statements. Kotlin: 'val result = getValue()?.also { name = it } != null'. Requires nullable types and scope functions. Python: 'result = getValue(); assigned = result is not None; name = result if assigned else name'. Manual and error-prone. EK9: 'result <- name ?= getValue()' is a single expression combining assignment and Boolean result.","keywords":["assignment","boolean","check","conditional","expression","guard","isset","null-safe","operator","result","return","safe","source","standalone"],"primaryTopics":[],"typicalErrors":[{"error":"E50030","correct":"guardResult <- name ?= getValue()","incorrect":"guardResult as String: name ?= getValue()","explanation":"The guard expression ?= returns a Boolean, not the type of the assigned value. Declaring the result as String triggers E50030 because Boolean and String are incompatible. Use type inference with '<-' to get the correct Boolean type. See ek9 -h E50030 for details."}],"companions":[]}
{"id":760,"category":"Control Flow","question":"How do I use guarded assignment :=? on an external object field?","url":"https://ek9.io/qa/QA0760.html","alternatePhrasings":["Can I use :=? on a record field via dot access?","How does guarded assignment work on config.host?","What does object.field :=? value do in EK9?"],"answer":"The guarded assignment operator (:=?) works on external object fields accessed via dot notation. It checks whether the field is currently SET, and only assigns if the field is UNSET. This is useful for providing default values to record or class fields without overwriting existing values.\n\nBASIC EXTERNAL FIELD GUARD\nAssign default values to record fields:\n  config <- Config()\n  config.host :=? \"localhost\"\n  config.port :=? 8080\nIf host and port are unset (default-constructed), the defaults are applied.\n\nPRESERVING EXISTING VALUES\nIf a field already has a value, the guarded assignment is skipped:\n  config <- Config()\n  config.host :=? \"localhost\"\n  config.host :=? \"should-not-overwrite\"\nAfter both lines, config.host is still \"localhost\" because the second :=? finds the field already set.\n\nCONFIGURATION PATTERN\nA common use is applying layered defaults to a configuration object:\n  config <- loadFromFile()\n  config.host :=? loadFromEnv(\"HOST\")\n  config.port :=? loadFromEnv(\"PORT\")\n  config.host :=? \"localhost\"\n  config.port :=? 8080\nFile values win, then environment, then hardcoded defaults.\n\nDIFFERENCE FROM LOCAL VARIABLE :=?\nThe semantics are identical — assign only when unset. The difference is that the compiler generates field access (getfield/putfield) rather than local variable access. Both check the isSet state of the target.\n\nSee Q79 for guarded assignment on local variables. See Q22 for variable declaration. See Q104 for uninitialised properties in classes.","ek9Example":"defines module qa.flow.guard.external.field\n\n  defines record\n\n    Config\n      host <- String()\n      port <- Integer()\n\n      default operator ?\n\n  defines program\n\n    GuardedExternalFieldDemo()\n      stdout <- Stdout()\n\n      // === BASIC EXTERNAL FIELD GUARD ===\n\n      config <- Config()\n      config.host :=? \"localhost\"\n      config.port :=? 8080\n      stdout.println(config.host)\n      stdout.println(config.port)\n\n      // === PRESERVING EXISTING VALUES ===\n\n      config.host :=? \"should-not-overwrite\"\n      config.port :=? 9090\n      stdout.println(config.host)\n      stdout.println(config.port)\n\n      stdout.println(\"Done\")","migrationContext":"Java: No direct equivalent. Must write: 'if (config.getHost() == null) { config.setHost(\"localhost\"); }'. Requires getter/setter boilerplate and manual null check. Kotlin: 'config.host = config.host ?: \"localhost\"'. Elvis operator handles null but requires reading the field twice. Go: 'if config.Host == \"\" { config.Host = \"localhost\" }'. Must know the zero value for each type. EK9: 'config.host :=? \"localhost\"' is a single operator that checks the field's isSet state and conditionally assigns. Works uniformly across all types.","keywords":["access","assignment","conditional","config","default","dot","external","field","guarded","isset","object","operator","property","record","safe","unset"],"primaryTopics":[],"typicalErrors":[{"error":"E50030","correct":"config.host :=? \"localhost\"\n      config.port :=? 8080","incorrect":"config.host :=? 42\n      config.port :=? \"not a number\"","explanation":"The guarded assignment operator :=? checks the target field's isSet state and conditionally assigns. The assigned value must be type-compatible with the field. Assigning an Integer to a String field (or vice versa) triggers E50030. See ek9 -h E50030 for details."}],"companions":[]}
{"id":761,"category":"Code Quality","question":"What is the LCOM4 cohesion limit and how do I stay within it?","url":"https://ek9.io/qa/QA0761.html","alternatePhrasings":["What triggers E11014 low cohesion?","What is the LCOM4 threshold for classes?","How does EK9 measure class cohesion?","Why does EK9 limit disconnected method groups?"],"answer":"EK9 measures class cohesion using LCOM4 (Lack of Cohesion of Methods version 4). It builds a graph of method-to-field relationships — methods sharing at least one field are connected. The number of disconnected subgraphs is the LCOM4 score.\n\nTHRESHOLDS\n- Class: max LCOM4 of 8 (E11014)\n- Service: max LCOM4 of 10\n- Component: max LCOM4 of 10\n\nWHAT LCOM4 MEASURES\nLCOM4 = 1 means perfect cohesion — all methods work together through shared fields. LCOM4 = 8 means 8 groups of methods that don't share any fields. Above the threshold, the class is doing too many unrelated things.\n\nTHIS EXAMPLE\nThe InventoryTracker class below has exactly 8 disconnected method groups (LCOM4 = 8), which is the maximum allowed for a class. Each method group accesses only its own field. Adding a 9th unrelated method would trigger E11014.\n\nHOW TO FIX HIGH LCOM4\n1. Extract each disconnected group into its own focused class\n2. Add methods that bridge groups by accessing multiple fields\n3. Consider if the class has a single clear purpose\n\nSee Q310 for code quality overview. See Q314 for cohesion and coupling details. See Q696 for complexity limits.","ek9Example":"defines module qa.codequality.lcom4boundary\n\n  defines class\n\n    <?-\n      This class has exactly 8 disconnected method groups (LCOM4 = 8).\n      Each method accesses only one unique field.\n      No method shares a field with another method.\n      This is exactly at the class threshold — it compiles.\n      Adding a 9th unrelated method/field pair would trigger E11014.\n    -?>\n    InventoryTracker\n      field1 Integer: 1\n      field2 Integer: 2\n      field3 Integer: 3\n      field4 Integer: 4\n      field5 Integer: 5\n      field6 Integer: 6\n      field7 Integer: 7\n      field8 Integer: 8\n\n      method1()\n        <- rtn Integer := field1\n\n      method2()\n        <- rtn Integer := field2\n\n      method3()\n        <- rtn Integer := field3\n\n      method4()\n        <- rtn Integer := field4\n\n      method5()\n        <- rtn Integer := field5\n\n      method6()\n        <- rtn Integer := field6\n\n      method7()\n        <- rtn Integer := field7\n\n      method8()\n        <- rtn Integer := field8\n\n      override operator ? as pure\n        <- rtn Boolean := field1?\n\n  defines program\n\n    Lcom4BoundaryDemo()\n      stdout <- Stdout()\n      tracker <- InventoryTracker()\n      stdout.println($tracker.method1())\n      stdout.println($tracker.method8())","migrationContext":"Java: SonarQube measures LCOM but only as advisory metric. C#: NDepend reports LCOM informational only. Python: no cohesion analysis. Go: no cohesion measurement. Kotlin: Detekt does not measure LCOM. EK9: LCOM4 > 8 is a compiler error — class won't compile.","keywords":["E11014","LCOM4","boundary","class","cohesion","disconnect","field","method","metric","quality","split","threshold"],"primaryTopics":["LCOM4","cohesion","class design","code quality"],"typicalErrors":[{"error":"E11014","correct":"      field8 Integer: 8\n\n      method1()","incorrect":"      field8 Integer: 8\n      field9 Integer: 9\n\n      method9()\n        <- rtn Integer := field9\n\n      method1()","explanation":"Adding field9 and method9 creates a 9th disconnected method group (method9 accesses only field9). LCOM4 goes from 8 to 9, exceeding the class threshold of 8. Extract unrelated methods into separate classes. See ek9 -h E11014 for details."}],"companions":[]}
{"id":762,"category":"Code Quality","question":"What is the efferent coupling limit and how do I stay within it?","url":"https://ek9.io/qa/QA0762.html","alternatePhrasings":["What triggers E11015 excessive coupling?","How many types can a class depend on?","What is the Ce threshold in EK9?","Why does EK9 limit type dependencies?"],"answer":"EK9 measures efferent coupling (Ce) — the number of distinct external types a construct depends on. When Ce exceeds the threshold, E11015 triggers.\n\nTHRESHOLDS\n- Class: max Ce of 12\n- Record: max Ce of 8\n- Trait: max Ce of 8\n- Service: max Ce of 15\n- Component: max Ce of 15\n- Function: max Ce of 8\n\nTHIS EXAMPLE\nThe HighCouplingClass below depends on exactly 12 distinct record types via fields. This is at the class threshold. Adding a 13th dependency triggers E11015.\n\nSee Q310 for code quality overview. See Q314 for cohesion and coupling details.","ek9Example":"defines module qa.codequality.couplingboundary\n\n  defines record\n\n    Dep1\n      mainValue Integer: 0\n      default operator ?\n    Dep2\n      mainValue Integer: 0\n      default operator ?\n    Dep3\n      mainValue Integer: 0\n      default operator ?\n    Dep4\n      mainValue Integer: 0\n      default operator ?\n    Dep5\n      mainValue Integer: 0\n      default operator ?\n    Dep6\n      mainValue Integer: 0\n      default operator ?\n    Dep7\n      mainValue Integer: 0\n      default operator ?\n    Dep8\n      mainValue Integer: 0\n      default operator ?\n    Dep9\n      mainValue Integer: 0\n      default operator ?\n    Dep10\n      mainValue Integer: 0\n      default operator ?\n    Dep11\n      mainValue Integer: 0\n      default operator ?\n    Dep12\n      mainValue Integer: 0\n      default operator ?\n    Dep13\n      mainValue Integer: 0\n      default operator ?\n\n  defines class\n\n    HighCouplingClass\n      d1 as Dep1: Dep1()\n      d2 as Dep2: Dep2()\n      d3 as Dep3: Dep3()\n      d4 as Dep4: Dep4()\n      d5 as Dep5: Dep5()\n      d6 as Dep6: Dep6()\n      d7 as Dep7: Dep7()\n      d8 as Dep8: Dep8()\n      d9 as Dep9: Dep9()\n      d10 as Dep10: Dep10()\n      d11 as Dep11: Dep11()\n      d12 as Dep12: Dep12()\n\n      allSet()\n        <- rtn as Boolean: d1? and d2? and d3? and d4? and d5? and d6? and d7? and d8? and d9? and d10? and d11? and d12?\n\n      default operator ?\n\n  defines program\n\n    CouplingBoundaryDemo()\n      stdout <- Stdout()\n      tracker <- HighCouplingClass()\n      stdout.println($tracker.allSet())","migrationContext":"Java: no coupling limit in javac; SonarQube reports Ce as informational. C#: NDepend measures Ce but does not enforce. Python: no coupling metrics. Go: no coupling enforcement. EK9: Ce exceeding threshold is a compiler error.","keywords":["Ce","E11015","boundary","class","coupling","dependency","efferent","quality","threshold","type"],"primaryTopics":["coupling","efferent coupling","type dependencies","code quality"],"typicalErrors":[{"error":"E11015","correct":"      d12 as Dep12: Dep12()\n\n      allSet()","incorrect":"      d12 as Dep12: Dep12()\n      d13 as Dep13: Dep13()\n\n      allSet()","explanation":"Adding field d13 of type Dep13 pushes Ce from 12 to 13, exceeding the class threshold of 12. Group related dependencies into a record or split the class. See ek9 -h E11015 for details."}],"companions":[]}
{"id":763,"category":"Code Quality","question":"What is the maximum inheritance depth allowed in EK9?","url":"https://ek9.io/qa/QA0763.html","alternatePhrasings":["What triggers E11019 excessive inheritance?","How deep can I extend classes in EK9?","What is the DIT limit for classes?","Why does EK9 limit inheritance depth?"],"answer":"EK9 enforces Depth of Inheritance Tree (DIT) limits per construct type. When a hierarchy exceeds the limit, E11019 triggers.\n\nTHRESHOLDS\n- Class: max DIT of 4\n- Record: max DIT of 2\n- Trait: max DIT of 4\n- Function: max DIT of 3\n- Component: max DIT of 4\n\nHOW DIT IS COUNTED\nDIT counts the number of extends relationships from the root to the leaf. The implicit Any base is not counted. So Level1 -> Level2 -> Level3 -> Level4 -> Level5 has DIT = 4.\n\nTHIS EXAMPLE\nThe class hierarchy below has exactly DIT = 4 (5 levels including root). This is the maximum allowed. Adding Level6 extending Level5 would trigger E11019.\n\nWHY LIMIT DEPTH\n- Research by Chidamber and Kemerer: classes with DIT > 5 have disproportionately more defects\n- Deep hierarchies create fragile base class problems\n- Each level adds coupling — changes ripple through all descendants\n- Prefer composition over deep inheritance\n\nHOW TO REDUCE DEPTH\n1. Use composition/delegation instead of inheritance\n2. Use traits for shared behaviour without hierarchy depth\n3. Flatten by extracting common fields into records\n\nSee Q310 for code quality overview. See Q315 for inheritance limits. See Q729 for boundary testing.","ek9Example":"defines module qa.codequality.inheritanceboundary\n\n  defines class\n\n    <?-\n      5-level class hierarchy with DIT = 4 (exactly at class threshold).\n      Level1 (root) -> Level2 -> Level3 -> Level4 -> Level5 (leaf)\n      Each level must be 'as open' to allow extension, except the leaf.\n    -?>\n    Level1 as open\n      value1 as Integer: 1\n\n      getSum()\n        <- rtn as Integer: value1\n\n      default operator ?\n\n    Level2 extends Level1 as open\n      value2 as Integer: 2\n\n      override getSum()\n        <- rtn as Integer: value2\n\n      default operator ?\n\n    Level3 extends Level2 as open\n      value3 as Integer: 3\n\n      override getSum()\n        <- rtn as Integer: value3\n\n      default operator ?\n\n    Level4 extends Level3 as open\n      value4 as Integer: 4\n\n      override getSum()\n        <- rtn as Integer: value4\n\n      default operator ?\n\n    //DIT = 4, exactly at threshold — this compiles\n    Level5 extends Level4\n      value5 as Integer: 5\n\n      override getSum()\n        <- rtn as Integer: value5\n\n      default operator ?\n\n  defines program\n\n    InheritanceDepthDitDemo()\n      stdout <- Stdout()\n      leaf <- Level5()\n      stdout.println(`Sum: ${leaf.getSum()}`)","migrationContext":"Java: no DIT limit (Spring hierarchies often 6-8 deep). C#: no DIT limit (NDepend advisory only). C++: no limit (MFC hierarchies notoriously deep). Kotlin: no DIT limit. Python: no limit. EK9: DIT exceeding threshold is a compiler error.","keywords":["DIT","E11019","boundary","class","composition","depth","extends","hierarchy","inheritance","open","quality"],"primaryTopics":["inheritance depth","DIT","class hierarchy","code quality"],"typicalErrors":[{"error":"E11019","correct":"    Level5 extends Level4\n      value5 as Integer: 5\n\n      override getSum()\n        <- rtn as Integer: value5\n\n      default operator ?\n\n  defines program","incorrect":"    Level5 extends Level4 as open\n      value5 as Integer: 5\n\n      override getSum()\n        <- rtn as Integer: value5\n\n      default operator ?\n\n    Level6 extends Level5\n      value6 as Integer: 6\n\n      override getSum()\n        <- rtn as Integer: value6\n\n      default operator ?\n\n  defines program","explanation":"Adding Level6 extending Level5 pushes the hierarchy depth from 4 to 5, exceeding the class threshold of 4. Use composition instead of deeper inheritance. See ek9 -h E11019 for details."}],"companions":[]}
{"id":764,"category":"Code Quality","question":"How does EK9 detect confusingly similar variable names?","url":"https://ek9.io/qa/QA0764.html","alternatePhrasings":["What triggers E11030 similar names?","Why does EK9 flag variables with similar names?","How close can variable names be in EK9?","What is the Levenshtein distance check for names?"],"answer":"EK9 checks for variables in the same scope whose names differ by only 1-2 characters (Levenshtein distance). When detected, E11030 triggers.\n\nWHAT IT DETECTS\nTwo variables like 'count' and 'coumt' differ by one character — a typo waiting to happen. Similarly 'index' and 'indx' or 'total' and 'totl'. When a developer (or AI) references the wrong one, the bug is subtle and hard to spot.\n\nWHAT IS ALLOWED\n- Names differing by 3+ characters are fine\n- Prefixed/suffixed variants are fine: 'name' and 'firstName'\n- Single-letter names in loop context: 'i', 'x', 'y'\n- Different scopes: same-name variables in different functions\n\nTHIS EXAMPLE\nThe function below uses 'totalCount' and 'itemCount' — these differ by enough characters (Levenshtein distance > 2) to be clearly distinguishable. Using 'totalCount' and 'totalCoumt' would trigger E11030.\n\nWHY THIS MATTERS\n- AI assistants generate code at speed — similar names cause subtle bugs\n- Code review misses single-character differences\n- Autocomplete in editors may pick the wrong variable\n- Debugging similar-name bugs wastes significant time\n\nSee Q290 for banned names. See Q292 for naming conventions. See Q310 for quality overview.","ek9Example":"defines module qa.codequality.similarnames\n\n  defines function\n\n    <?-\n      This function uses clearly distinct variable names.\n      'totalCount' and 'itemCount' differ by enough characters\n      to be clearly distinguishable (Levenshtein distance > 2).\n      Using 'totalCount' and 'totalCoumt' would trigger E11030.\n    -?>\n    calculateTotals()\n      ->\n        items as List of Integer\n      <-\n        result as String: \"empty\"\n\n      totalCount <- 0\n      itemCount <- 0\n      grandTotal <- 0\n\n      for item in items\n        itemCount++\n        totalCount++\n        grandTotal += item\n\n      result: `Items: ${itemCount}, Count: ${totalCount}, Total: ${grandTotal}`\n\n  defines program\n\n    SimilarNamesBoundaryDemo()\n      stdout <- Stdout()\n      numbers <- [10, 20, 30, 40, 50]\n      stdout.println(calculateTotals(numbers))","migrationContext":"Java: no similar-name detection. C#: no similar-name detection. Python: no similar-name detection. Go: no similar-name detection. Rust: no similar-name detection. All languages rely on code review to catch confusingly similar names. EK9: similar names (Levenshtein ≤ 2) are a compiler error.","keywords":["E11030","Levenshtein","confusing","distance","names","naming","quality","similar","typo","variable"],"primaryTopics":["naming","similar names","Levenshtein distance","code quality"],"typicalErrors":[{"error":"E11030","correct":"      totalCount <- 0\n      itemCount <- 0\n      grandTotal <- 0\n\n      for item in items\n        itemCount++\n        totalCount++\n        grandTotal += item\n\n      result: `Items: ${itemCount}, Count: ${totalCount}, Total: ${grandTotal}`","incorrect":"      totalCount <- 0\n      totalCont <- 0\n      grandTotal <- 0\n\n      for item in items\n        totalCont++\n        totalCount++\n        grandTotal += item\n\n      result: `Items: ${$totalCont}, Count: ${$totalCount}, Total: ${$grandTotal}`","explanation":"Renaming 'itemCount' to 'totalCont' makes it confusingly similar to 'totalCount' (Levenshtein distance 2). Both are used in the same scope, creating risk of referencing the wrong one. Use clearly distinct names. See ek9 -h E11030 for details."}],"companions":[]}
{"id":765,"category":"Code Quality","question":"What is the cognitive complexity limit in EK9?","url":"https://ek9.io/qa/QA0765.html","alternatePhrasings":["What triggers E11021 excessive cognitive complexity?","How does EK9 measure cognitive complexity?","What is the difference between cyclomatic and cognitive complexity?","Why does EK9 limit how hard code is to understand?"],"answer":"EK9 measures cognitive complexity — how difficult code is to understand, not just how many paths exist. When it exceeds 35, E11021 triggers.\n\nHOW IT DIFFERS FROM CYCLOMATIC COMPLEXITY\n- Cyclomatic: counts decision points (each if/while/for = +1)\n- Cognitive: adds penalties for NESTING depth (nested if = +depth, not +1)\n\nNESTING MULTIPLIER\nEach nesting level multiplies the cost:\n- Level 0: if = +1\n- Level 1: nested if = +2\n- Level 2: doubly nested if = +3\n\nHOW TO REDUCE COGNITIVE COMPLEXITY\n1. Extract nested blocks into helper functions (resets nesting)\n2. Use guard expressions to flatten conditional chains\n3. Replace complex boolean expressions with named predicates\n\nSee Q312 for complexity metrics overview. See Q728 for nesting depth limits.","ek9Example":"defines module qa.codequality.cognitiveboundary\n\n  defines function\n\n    checkA()\n      -> number as Integer\n      <- rtn as Boolean: number > 0\n\n    checkB()\n      -> number as Integer\n      <- rtn as Boolean: number > 0\n\n    checkC()\n      -> number as Integer\n      <- rtn as Boolean: number > 0\n\n    <?-\n      This function has cognitive complexity near the threshold of 35.\n      Each nested level costs more than the last due to the nesting multiplier.\n    -?>\n    evaluateItems()\n      ->\n        items as List of Integer\n        count as Integer\n        enabled as Boolean\n      <-\n        message as String: \"none\"\n\n      //Level 1: +1 each\n      if checkA(count)\n        //Level 2: +2 each\n        if checkB(count)\n          //Level 3: +3\n          if enabled\n            //Level 4: for = +4\n            for item in items\n              //Level 5: +5\n              if checkC(item)\n                message: \"deep-a\"\n              else\n                message: \"deep-b\"\n          else\n            message: \"no-flag\"\n        else\n          if enabled\n            for item in items\n              if checkA(item)\n                message: \"deep-c\"\n              else\n                message: \"deep-d\"\n          else\n            message: \"low\"\n      else\n        //Level 2: +2 each\n        if checkB(count)\n          message: \"alt-a\"\n        else\n          message: \"default\"\n\n  defines program\n\n    CognitiveComplexityDemo()\n      stdout <- Stdout()\n      numbers <- [1, 2, 3]\n      stdout.println(evaluateItems(numbers, 5, true))\n      stdout.println(evaluateItems(numbers, 0, false))","migrationContext":"Java: SonarQube measures cognitive complexity (advisory, default threshold 15). C#: NDepend measures cognitive complexity (informational). Python: no cognitive complexity in standard tools. EK9: cognitive complexity > 35 is a compiler error.","keywords":["E11021","cognitive","complexity","metric","nesting","quality","readability","threshold","understanding"],"primaryTopics":["cognitive complexity","readability","nesting cost","code quality"],"typicalErrors":[{"error":"E11021","correct":"        if checkB(count)\n          message: \"alt-a\"\n        else\n          message: \"default\"","incorrect":"        if checkB(count)\n          if enabled\n            for item in items\n              if checkC(item)\n                message: \"alt-a\"\n              else\n                message: \"alt-b\"\n          else\n            message: \"alt-low\"\n        else\n          message: \"default\"","explanation":"Adding nested if at depth 6 adds +6 cognitive complexity, pushing the function past the threshold of 35. Each nested level costs +depth. Extract deep logic into helper functions. See ek9 -h E11021 for details."}],"companions":[]}
{"id":766,"category":"Code Quality","question":"What is the module coupling limit in EK9?","url":"https://ek9.io/qa/QA0766.html","alternatePhrasings":["What triggers E11016 excessive module coupling?","How many external modules can I reference?","What is the module-level coupling threshold?","Why does EK9 limit cross-module dependencies?"],"answer":"EK9 limits the number of external modules a single module can depend on. When a module references more than 10 external modules, E11016 triggers.\n\nWHAT IT MEASURES\nModule coupling counts the distinct external module namespaces referenced via 'references' declarations. This is different from type-level coupling (Ce) — it measures architectural-level dependencies.\n\nTHRESHOLD\n- Maximum: 10 external module references per module\n\nWHY LIMIT MODULE COUPLING\n- High module coupling creates brittle architectures\n- Changes in one module ripple through many dependents\n- Makes the module hard to understand in isolation\n- Indicates the module is doing too many things\n\nHOW TO REDUCE MODULE COUPLING\n1. Split the module into focused sub-modules\n2. Create a facade module that re-exports needed types\n3. Use dependency injection to decouple\n4. Apply the Interface Segregation Principle\n\nTHIS EXAMPLE\nThe module below demonstrates a self-contained module without external references. In a real project, you would add 'references' to declare dependencies on other modules.\n\nSee Q310 for code quality overview. See Q314 for cohesion and coupling details. See Q762 for type-level coupling.","ek9Example":"defines module qa.codequality.modulecoupling\n\n  defines function\n\n    <?-\n      Helper function demonstrating self-contained module design.\n      In practice, high module coupling comes from too many 'references'\n      declarations pointing to external modules.\n    -?>\n    calculateTotal()\n      -> numbers as List of Integer\n      <- total as Integer: 0\n      for num in numbers\n        total += num\n\n  defines program\n\n    ModuleCouplingDemo()\n      stdout <- Stdout()\n      numbers <- [10, 20, 30, 40, 50]\n      stdout.println(`Total: ${calculateTotal(numbers)}`)","migrationContext":"Java: Maven/Gradle track module dependencies but don't enforce count limits. C#: NuGet package count is unchecked. Python: import count is unchecked. Go: module dependency count is unchecked. Rust: crate dependency count is unchecked. EK9: > 10 external module references is a compiler error.","keywords":["E11016","architecture","coupling","dependency","external","module","quality","references","threshold"],"primaryTopics":["module coupling","architecture","dependencies","code quality"],"typicalErrors":[{"error":"E50001","correct":"      stdout.println(`Total: ${calculateTotal(numbers)}`)","incorrect":"      stdout.println(`Total: ${calcTotal(numbers)}`)","explanation":"The function calcTotal is not defined in this module. The correct name is calculateTotal. See ek9 -h E50001 for details."}],"companions":[]}
{"id":767,"category":"Code Quality","question":"What is the dev context and how does it relate to testing in EK9?","url":"https://ek9.io/qa/QA0767.html","alternatePhrasings":["Why can I only use assert in @Test programs?","What is the difference between dev and production builds?","How does EK9 separate test code from production code?","What triggers E81012 production assertion?","Why does EK9 restrict assert to dev context?"],"answer":"EK9 enforces a strict compile-time boundary between test code and production code using the 'dev context' concept.\n\nTHE DEV CONTEXT\nWhen you compile with -cd, -Cd, or run tests with -t, the compiler includes dev code (programs marked with @Test and all code they reach). When you compile with -c or -C (production builds), @Test programs and their assertions are stripped entirely.\n\nASSERT VS REQUIRE\n- assert: Test-only. Validates expected outcomes. Only allowed in code reachable from @Test programs. Triggers E81012 if found in production code paths.\n- require: Production-safe. Enforces preconditions. Works everywhere. Use this in production functions and methods.\n\nTHE CALL GRAPH ENFORCES THIS\nEK9 performs call graph analysis from all program entry points. If a non-@Test program can reach code containing assert, the compiler raises E81012 (PRODUCTION_ASSERTION). If assert exists in code NOT reachable from any program (test or production), it raises E81011 (ORPHAN_ASSERTION).\n\nWHY THIS MATTERS\n1. assert has overhead (expression capture, stack traces) — not wanted in production\n2. Mixing test and production validation indicates confused design intent\n3. Production code should use require for preconditions, not test assertions\n4. Stripping test code from production builds reduces binary size and attack surface\n\nTHIS EXAMPLE\nThe main file has the production program and functions using require. The companion dev file (QA0767_dev_tests.ek9) has the @Test programs using assert. The dev file is only compiled in dev builds (-cd, -t).\n\nSee Q155 for writing tests. See Q156 for assertions. See Q157 for running tests. See Q305 for require vs assert vs throw.","ek9Example":"defines module qa.codequality.devcontext\n\n  defines function\n\n    <?-\n      This function uses 'require' for preconditions.\n      It is called ONLY from the PRODUCTION program below.\n      If assert were used instead of require, E81012 would trigger\n      because the call graph shows it is reachable from non-@Test code.\n    -?>\n    validateInput()\n      -> input as Integer\n      <- valid as Boolean: false\n\n      require input?\n      require input > 0\n\n      valid: true\n\n    <?-\n      Helper for tests only — called exclusively from @Test programs\n      in the companion dev file.\n    -?>\n    doubleValue()\n      -> input as Integer\n      <- result as Integer: input * 2\n\n  defines program\n\n    <?-\n      PRODUCTION PROGRAM — no @Test directive.\n      Calls validateInput which uses require (correct).\n      If validateInput used assert, E81012 would fire.\n    -?>\n    ProductionEntry()\n      stdout <- Stdout()\n\n      if validateInput(42)\n        stdout.println(\"Input is valid\")","migrationContext":"Java: assert works everywhere but disabled by default (-ea to enable), JUnit assertions are library methods callable from anywhere — no compile-time enforcement. Python: assert works everywhere, stripped with -O flag — no compile-time boundary. Rust: assert! works everywhere, debug_assert! stripped in release builds — but no call graph analysis to enforce boundaries. Go: no assert keyword, testing.T methods technically callable from non-test code. EK9: compile-time call graph analysis enforces that assert only appears in code reachable from @Test programs.","keywords":["@Test","E81011","E81012","assert","boundary","call-graph","compile","context","dev","production","require","test"],"primaryTopics":["dev context","test vs production","assert vs require","call graph"],"typicalErrors":[{"error":"E81015","correct":"      require input?\n      require input > 0","incorrect":"      assert input?\n      assert input > 0","explanation":"Using assert in a non-dev source file triggers E81015 TEST_CONSTRUCT_IN_NON_DEV_SOURCE. The assert keyword is only allowed in files within a dev/ directory or matching the QA####_dev_ naming convention. Use require for production preconditions. See ek9 -h E81015 for details."}],"companions":[{"filename":"QA0767_dev_tests.ek9","source":"#!ek9\ndefines module qa.codequality.devcontext\n\n  defines program\n\n    //TEST PROGRAM — marked with @Test.\n    //This program only exists in dev builds (-cd, -t).\n    //It CAN use assert because it is a test program.\n    @Test\n    TestDoubleValue()\n      stdout <- Stdout()\n\n      result <- doubleValue(5)\n      assert result == 10\n\n      result2 <- doubleValue(7)\n      assert result2 == 14\n\n      stdout.println(\"doubleValue tests passed\")\n\n//EOF\n","dev":true}]}
{"id":768,"category":"Classes and OOP","question":"Why can't an abstract method have a body in EK9?","url":"https://ek9.io/qa/QA0768.html","alternatePhrasings":["What triggers E07100 abstract but body provided?","How do I define abstract methods in EK9?","What is the difference between abstract and concrete methods in EK9?"],"answer":"Abstract methods declare a signature that subclasses must implement. Providing a body contradicts the meaning of abstract — if you provide an implementation, it is not abstract.\n\nABSTRACT METHOD RULES\n1. Mark the method 'as abstract' — no implementation body\n2. The containing class must be 'abstract' or 'as open'\n3. Subclasses must override and provide the implementation\n\nTHIS EXAMPLE\nThe Shape class below is abstract with one abstract method (area) and one concrete method (describe). The Circle subclass overrides area() with an actual implementation. This is the correct pattern.\n\nCOMMON MISTAKE\nAI and developers migrating from other languages sometimes write 'as abstract' on a method and then add a body. EK9 treats this as error E07100 because abstract and implementation are mutually exclusive.\n\nHOW TO FIX\n1. Remove 'as abstract' if you want the method to have an implementation\n2. Remove the body if you want the method to be abstract\n\nSee Q93 for defining classes. See Q84 for making classes extensible. See Q86 for abstract classes. See Q274 for common AI mistakes.","ek9Example":"defines module qa.classesandoop.abstractmethods\n\n  defines function\n\n    Transformer() as abstract\n      -> input as String\n      <- output as String?\n\n    UpperTransformer() extends Transformer\n      -> input as String\n      <- output as String: input.upperCase()\n\n  defines class\n\n    Shape as abstract\n      name <- String()\n\n      Shape()\n        -> n as String\n        name :=: n\n\n      area() as abstract\n        <- rtn as Float?\n\n      validate() as abstract\n        -> input as String\n\n      describe()\n        <- rtn as String: name\n\n      default operator ?\n\n    Circle extends Shape\n      radius <- Float()\n\n      Circle()\n        -> r as Float\n        super(\"circle\")\n        radius :=: r\n\n      override area()\n        <- rtn as Float: radius * radius * 3.14159\n\n      override validate()\n        -> input as String\n        require input?\n\n      default operator ?\n\n  defines program\n\n    ShowShapes()\n      stdout <- Stdout()\n      circle <- Circle(5.0)\n      if circle?\n        stdout.println(\"Shape: \" + circle.describe())\n        circle.validate(\"test\")\n        result <- UpperTransformer(\"hello\")\n        stdout.println(result)","migrationContext":"Java: abstract methods have no body, concrete methods have body — same rule but Java uses braces. Python: raise NotImplementedError() convention for abstract, or abc.abstractmethod decorator. Rust: trait methods without default body are abstract. Kotlin: abstract fun has no body, regular fun has body. C#: abstract methods have no body, virtual methods have body. EK9: same semantics as Java/Kotlin, 'as abstract' modifier with no body allowed.","keywords":["E07100","abstract","body","class","concrete","implement","method","override","signature","subclass"],"primaryTopics":["abstract methods","E07100","class design"],"typicalErrors":[{"error":"E07100","correct":"      area() as abstract\n        <- rtn as Float?","incorrect":"      area() as abstract\n        <- rtn as Float: 0.0","explanation":"Adding an initialisation value '0.0' to the return variable creates a method body. Abstract methods must have no implementation — just a signature with unset return type. Remove the initialisation or remove 'as abstract'. See ek9 -h E07100 for details."},{"error":"E07100","correct":"      validate() as abstract\n        -> input as String","incorrect":"      validate() as abstract\n        -> input as String\n        require input?","explanation":"Adding a require statement creates a body. Even a single precondition check is an implementation. Abstract methods must be pure signatures — the subclass provides the body including any preconditions. See ek9 -h E07100 for details."},{"error":"E07100","correct":"    Transformer() as abstract\n      -> input as String\n      <- output as String?","incorrect":"    Transformer() as abstract\n      -> input as String\n      <- output as String: input","explanation":"Initialising the return variable in an abstract function creates a body. Abstract functions must declare an unset return type — the implementing function provides the logic. See ek9 -h E07100 for details."},{"error":"E07020","correct":"      override area()\n        <- rtn as Float: radius * radius * 3.14159","incorrect":"      override area() as abstract\n        <- rtn as Float?","explanation":"Combining 'override' with 'as abstract' is contradictory. Override means 'I am implementing this', abstract means 'I am NOT implementing this'. Choose one: override with a body, or abstract without. See ek9 -h E07020 for details."}],"companions":[]}
{"id":769,"category":"Classes and OOP","question":"Why does EK9 require an explicit constructor when properties are uninitialized?","url":"https://ek9.io/qa/QA0769.html","alternatePhrasings":["What triggers E07170 explicit constructor required?","How do I fix uninitialised property errors in EK9?","Why can't I declare a property without a value in EK9?"],"answer":"EK9 requires that every property has a known value before use. When you declare a property as unset (using 'as Type?' instead of '<- value'), the compiler cannot auto-generate a constructor because it does not know what values to assign.\n\nTWO PROPERTY STYLES\n1. Initialised: 'name <- \"default\"' — value known, default constructor works\n2. Uninitialised: 'name as String?' — value unknown, explicit constructor REQUIRED\n\nTHIS EXAMPLE\nThe User class initialises both properties with '<-'. This allows 'default operator ?' and no explicit constructor is needed. The mutation removes the initialisation, creating an uninitialised property that triggers E07170.\n\nWHY THIS RULE EXISTS\nJava allows null fields with no warning. This causes NullPointerException at runtime when fields are used before being set. EK9 moves this check to compile time — if a property has no value, you must provide a constructor that gives it one.\n\nHOW TO FIX\n1. Initialise the property at declaration: 'name <- \"unknown\"'\n2. Or provide an explicit constructor that sets it\n\nSee Q93 for defining classes. See Q89 for constructors. See Q99 for why explicit constructors matter.","ek9Example":"defines module qa.classesandoop.explicitconstructor\n\n  defines class\n\n    User\n      name <- String()\n      active <- true\n\n      describe()\n        <- rtn as String: `${name} active=${active}`\n\n      default operator ?\n\n    Item\n      title <- String()\n      count <- 0\n\n      getTitle()\n        <- rtn as String: title\n\n      default operator ?\n\n  defines program\n\n    ShowUser()\n      stdout <- Stdout()\n      user <- User()\n      if user?\n        stdout.println(user.describe())\n      item <- Item()\n      if item?\n        stdout.println(item.getTitle())","migrationContext":"Java: fields default to null/0, no compile-time enforcement. Python: __init__ sets fields, no enforcement for completeness. Rust: all struct fields must be initialised at creation, no partial init. Kotlin: lateinit defers but crashes at runtime if used before init. Go: zero values for all fields. EK9: uninitialised properties require explicit constructor — compile-time enforcement.","keywords":["E07170","class","constructor","explicit","field","initialise","null","property","required","uninitialised"],"primaryTopics":["constructors","E07170","property initialization"],"typicalErrors":[{"error":"E07170","correct":"      name <- String()","incorrect":"      name as String?","explanation":"Changing 'name <- String()' (initialised) to 'name as String?' (uninitialised) means the compiler cannot generate a default constructor. Either initialise the property or add an explicit constructor that sets it. See ek9 -h E07170 for details."},{"error":"E07170","correct":"      title <- String()","incorrect":"      title as String?","explanation":"Changing initialised property to uninitialised requires an explicit constructor. Without one, the compiler cannot ensure the property has a value before use. See ek9 -h E07170 for details."}],"companions":[]}
{"id":770,"category":"Classes and OOP","question":"Why can't I use 'by' delegation inside a trait?","url":"https://ek9.io/qa/QA0770.html","alternatePhrasings":["What triggers E07040 trait by identifier not supported?","How does trait delegation with by work in EK9?","Where can I use the by keyword for delegation?"],"answer":"The 'by' keyword delegates trait method implementations to a field. This only works in CLASSES, not in traits. Traits define interfaces and optional default implementations — they cannot hold fields or delegate to other objects.\n\nDELEGATION SYNTAX (CLASS ONLY)\n  MyClass with trait of SomeTrait by myField\nThis tells the compiler: when SomeTrait methods are called on MyClass, forward them to myField.\n\nWHY NOT IN TRAITS\nTraits are stateless contracts. Delegation requires a concrete field to forward to — traits have no fields. Attempting 'by' in a trait is a design error caught at compile time.\n\nTHIS EXAMPLE\nThe Renderable and Loggable traits define contracts. The Formatter class delegates Renderable to its helper field using 'by'. This is the correct pattern — delegation in a class.\n\nCOMMON MISTAKE\nDevelopers try to compose traits using 'by'. In EK9, traits compose through 'with trait of' (listing multiple traits) or 'extends' (trait hierarchy), never through delegation.\n\nSee Q78 for traits. See Q85 for composition. See Q93 for classes.","ek9Example":"defines module qa.classesandoop.traitdelegation\n\n  defines trait\n\n    Renderable\n      render()\n        <- rtn as String?\n\n    Loggable\n      log()\n        <- rtn as String?\n\n    Combined with trait of Renderable, Loggable\n\n  defines class\n\n    RenderHelper with trait of Renderable\n      label <- String()\n\n      RenderHelper()\n        -> l as String\n        label :=: l\n\n      override render()\n        <- rtn as String: label\n\n      default operator ?\n\n    Formatter with trait of Renderable by helper\n      helper as Renderable?\n\n      default private Formatter()\n\n      Formatter()\n        -> h as Renderable\n        require h?\n        helper: h\n\n      default operator ?\n\n  defines program\n\n    ShowDelegation()\n      stdout <- Stdout()\n      helper <- RenderHelper(\"formatted output\")\n      doc <- Formatter(helper)\n      if doc?\n        stdout.println(doc.render())","migrationContext":"Java: no delegation keyword, manual forwarding methods required. Kotlin: 'by' keyword works on interfaces in class declarations only. Rust: no delegation, use Deref or manual forwarding. Go: struct embedding provides implicit delegation. C#: no delegation keyword. EK9: 'by' delegation in class declarations only, not in traits.","keywords":["E07040","by","class","composition","delegate","delegation","field","forward","interface","trait"],"primaryTopics":["trait delegation","E07040","composition"],"typicalErrors":[{"error":"E07040","correct":"    Combined with trait of Renderable, Loggable","incorrect":"    Combined with trait of Renderable by renderer, Loggable","explanation":"Adding 'by renderer' to a trait definition attempts delegation inside a trait. Traits have no fields to delegate to — delegation only works in classes. Remove 'by renderer' or move the delegation to a class. See ek9 -h E07040 for details."}],"companions":[]}
{"id":771,"category":"Classes and OOP","question":"Why must a dispatcher method have a body in EK9?","url":"https://ek9.io/qa/QA0771.html","alternatePhrasings":["What triggers E07120 dispatcher but no body provided?","How do I write a dispatcher method in EK9?","What is the dispatcher pattern in EK9?"],"answer":"A dispatcher method routes calls to type-specific private overloads at runtime. The base dispatcher method MUST have a body — it serves as the default fallback when no specific overload matches the argument type.\n\nDISPATCHER PATTERN\n1. Declare a public method 'as dispatcher' taking a base type\n2. Provide a body — this is the default handler\n3. Add private overloads for specific types\n4. Runtime routes to the most specific match\n\nTHIS EXAMPLE\nThe TypeProcessor class has a dispatcher method handle() that takes Any. The body returns a default description. Private overloads handle Integer and String specifically. The runtime automatically routes to the correct overload.\n\nWHY A BODY IS REQUIRED\nUnlike abstract methods (which have no body), dispatchers need a default implementation for the fallback case — when the argument type doesn't match any specific overload. Without this, the dispatcher has no behaviour for unmatched types.\n\nCOMMON MISTAKE\nDevelopers assume dispatcher works like abstract — defining just the signature. But abstract means 'subclass provides implementation', while dispatcher means 'I route to overloads and handle the default case myself'.\n\nSee Q47 for dispatcher pattern. See Q51 for overloading vs dispatch. See Q768 for abstract methods (the opposite rule).","ek9Example":"defines module qa.classesandoop.dispatcherbody\n\n  defines class\n\n    TypeProcessor\n      handle() as dispatcher\n        -> item as Any\n        <- rtn as String: \"unhandled type\"\n\n      private handle()\n        -> item as Integer\n        <- rtn as String: `integer ${item}`\n\n      private handle()\n        -> item as String\n        <- rtn as String: `string ${item}`\n\n      default operator ?\n\n    Describer\n      describe() as dispatcher\n        -> target as Any\n        <- rtn as String: \"unknown\"\n\n      private describe()\n        -> target as Integer\n        <- rtn as String: `number ${target}`\n\n      default operator ?\n\n  defines program\n\n    ShowDispatcher()\n      stdout <- Stdout()\n      processor <- TypeProcessor()\n\n      items <- [1, \"hello\", 3.14]\n      for item in items\n        result <- processor.handle(item)\n        stdout.println(result)\n\n      describer <- Describer()\n      entries <- [99, true]\n      for entry in entries\n        output <- describer.describe(entry)\n        stdout.println(output)","migrationContext":"Java: no built-in dispatch, manual instanceof chains or visitor pattern. Python: functools.singledispatch for function-level dispatch. Kotlin: no built-in multi-dispatch, use sealed class + when. Rust: no multi-dispatch, use enum + match. Go: no dispatch, use type switch. EK9: 'as dispatcher' keyword with automatic runtime type routing.","keywords":["E07120","body","default","dispatch","dispatcher","method","overload","routing","runtime","type"],"primaryTopics":["dispatcher pattern","E07120","multiple dispatch"],"typicalErrors":[{"error":"E07120","correct":"      handle() as dispatcher\n        -> item as Any\n        <- rtn as String: \"unhandled type\"","incorrect":"      handle() as dispatcher\n        -> item as Any\n        <- rtn as String?","explanation":"Removing the initialisation from the return variable leaves the dispatcher with no body. Dispatcher methods must have a default implementation for the fallback case. Provide a body that handles unmatched types. See ek9 -h E07120 for details."},{"error":"E07120","correct":"      describe() as dispatcher\n        -> target as Any\n        <- rtn as String: \"unknown\"","incorrect":"      describe() as dispatcher\n        -> target as Any\n        <- rtn as String?","explanation":"The dispatcher describe() has no body — the return variable is declared but not initialised. Dispatchers are not abstract; they need a working default implementation. See ek9 -h E07120 for details."}],"companions":[]}
{"id":772,"category":"Classes and OOP","question":"Why does EK9 reject circular type hierarchies?","url":"https://ek9.io/qa/QA0772.html","alternatePhrasings":["What triggers E05020 circular hierarchy detected?","Why can't class A extend class B if B extends A?","How do I fix a circular inheritance chain?"],"answer":"EK9 detects when types form a cycle in their inheritance chain — A extends B, B extends C, C extends A. This is logically impossible: you cannot inherit from something that inherits from you.\n\nWHY CYCLES ARE IMPOSSIBLE\nInheritance means 'I am a specialisation of my parent'. If A extends B and B extends A, then A is a specialisation of B which is a specialisation of A — an infinite recursion with no base. Which constructor runs first? Which fields exist first? Undefined.\n\nTHIS EXAMPLE\nThe Vehicle, Car, ElectricCar hierarchy is a valid linear chain. Vehicle is the root, Car extends it, ElectricCar extends Car. No cycles.\n\nCOMMON CAUSES\n1. Refactoring that accidentally swapped inheritance direction\n2. Copy-paste errors when creating similar classes\n3. Two classes that need each other's features (use composition instead)\n\nHOW TO FIX\n1. Draw the inheritance relationships to visualise the cycle\n2. Identify the true root/base class\n3. Break the cycle by removing one 'extends'\n4. Consider composition over inheritance\n\nSee Q77 for inheritance. See Q85 for composition. See Q83 for closed types.","ek9Example":"defines module qa.classesandoop.circularhierarchy\n\n  defines class\n\n    Vehicle as open\n      make <- String()\n\n      Vehicle()\n        -> m as String\n        make :=: m\n\n      describe()\n        <- rtn as String: make\n\n      default operator ?\n\n    Car extends Vehicle as open\n      doors <- Integer()\n\n      Car()\n        ->\n          m as String\n          d as Integer\n        super(m)\n        doors :=: d\n\n      override describe()\n        <- rtn as String: `${super.describe()} ${doors}-door`\n\n      default operator ?\n\n    ElectricCar extends Car as open\n      range <- Integer()\n\n      ElectricCar()\n        ->\n          m as String\n          d as Integer\n          r as Integer\n        super(m, d)\n        range :=: r\n\n      override describe()\n        <- rtn as String: `${super.describe()} EV range=${range}`\n\n      default operator ?\n\n  defines program\n\n    ShowHierarchy()\n      stdout <- Stdout()\n      ev <- ElectricCar(\"Tesla\", 4, 350)\n      if ev?\n        stdout.println(ev.describe())","migrationContext":"Java: detects circular inheritance at compile time with 'cyclic inheritance involving X'. Python: detects at runtime with TypeError during C3 linearization. Rust: no inheritance, so no circular hierarchies possible. Go: no inheritance, composition only. Kotlin: same as Java, compile-time detection. EK9: compile-time detection at TYPE_HIERARCHY_CHECKS phase.","keywords":["E05020","chain","circular","class","composition","cycle","extends","hierarchy","inheritance","refactoring"],"primaryTopics":["circular hierarchy","E05020","inheritance"],"typicalErrors":[{"error":"E05020","correct":"    Car extends Vehicle as open","incorrect":"    Car extends ElectricCar as open","explanation":"Changing Car to extend ElectricCar creates a cycle: Car extends ElectricCar, ElectricCar extends Car. Draw the hierarchy and ensure it forms a tree with no loops. See ek9 -h E05020 for details."}],"companions":[]}
{"id":773,"category":"Classes and OOP","question":"Why must a method either have a body or be marked abstract?","url":"https://ek9.io/qa/QA0773.html","alternatePhrasings":["What triggers E07110 not abstract and no body provided?","How do I fix a method with no implementation in EK9?","Why does EK9 require explicit abstract keyword?"],"answer":"EK9 requires explicit intent for every method. A method with no body and no 'as abstract' keyword is ambiguous — did you forget to implement it, or did you intend it to be abstract? EK9 does not guess.\n\nTHE RULE\n- Method WITH body: concrete implementation (normal method)\n- Method WITHOUT body + 'as abstract': abstract, subclass must implement\n- Method WITHOUT body + NO abstract: ERROR E07110\n\nTHIS EXAMPLE\nThe Validator class has a concrete validate() method with a body. The Formatter function has a concrete body. Both are unambiguous.\n\nCOMMON CAUSES\n1. Forgot to add implementation body\n2. Forgot the 'as abstract' keyword\n3. Incomplete method definition during development\n\nTHIS PAIRS WITH E07100\nE07100 is the opposite: abstract WITH a body. Together they enforce that abstract and body are mutually exclusive — you must choose one.\n\nSee Q768 for abstract methods (opposite rule). See Q86 for abstract classes. See Q93 for defining classes.","ek9Example":"defines module qa.classesandoop.methodbody\n\n  defines function\n\n    Formatter() as pure\n      -> input as String\n      <- output as String: input.trim()\n\n  defines class\n\n    Validator\n      minLength <- Integer()\n\n      Validator()\n        -> min as Integer\n        minLength :=: min\n\n      validate()\n        -> input as String\n        <- rtn as Boolean: length input > 0\n\n      default operator ?\n\n  defines program\n\n    ShowValidation()\n      stdout <- Stdout()\n      checker <- Validator(3)\n      if checker?\n        stdout.println($checker.validate(\"hello\"))\n        stdout.println(Formatter(\"  padded  \"))","migrationContext":"Java: interface methods are implicitly abstract, class methods without body are compile errors. Python: no enforcement, pass or raise NotImplementedError by convention. Rust: trait methods without body are implicitly abstract. Kotlin: abstract keyword required on both class and method. Go: interface methods have no body, struct methods must have body. EK9: explicit 'as abstract' required — no implicit abstractions.","keywords":["E07110","abstract","body","concrete","explicit","function","implement","intent","method","missing"],"primaryTopics":["method body","E07110","explicit intent"],"typicalErrors":[{"error":"E07110","correct":"      validate()\n        -> input as String\n        <- rtn as Boolean: length input > 0","incorrect":"      validate()\n        -> input as String\n        <- rtn as Boolean?","explanation":"Removing the initialisation from the return variable leaves the method with no body. The method is not marked 'as abstract', so EK9 cannot determine intent. Either add an implementation or mark the method 'as abstract' (and make the class abstract). See ek9 -h E07110 for details."},{"error":"E07110","correct":"    Formatter() as pure\n      -> input as String\n      <- output as String: input.trim()","incorrect":"    Formatter() as pure\n      -> input as String\n      <- output as String?","explanation":"The function has no body — the return variable is declared but not initialised. Functions without a body must be marked 'as abstract'. Either provide an implementation or add 'as abstract'. See ek9 -h E07110 for details."}],"companions":[]}
{"id":774,"category":"Classes and OOP","question":"Why can't a constructor be abstract in EK9?","url":"https://ek9.io/qa/QA0774.html","alternatePhrasings":["What triggers E07050 abstract constructor?","Can I defer construction to a subclass in EK9?","Why does EK9 reject abstract on constructors?"],"answer":"Constructors create instances — that is their sole purpose. Marking a constructor 'abstract' says 'I have no implementation, a subclass must provide it'. But constructors MUST create an instance of their own class, so deferring that to a subclass is logically impossible.\n\nWHY ABSTRACT CONSTRUCTORS ARE IMPOSSIBLE\nWhen you call 'MyClass()', the constructor MUST create a MyClass instance. An abstract constructor would mean 'I do not create anything' — defeating the purpose. If you want polymorphic creation, use an abstract factory method instead.\n\nTHIS EXAMPLE\nThe Shape class is abstract with a concrete constructor that sets the name. The Circle subclass calls super() to initialise the base. Both constructors are concrete — they create their respective instances.\n\nRELATED CONSTRUCTOR RULES\n- E07050: abstract constructor (this error) — constructors must create instances\n- E07060: override constructor — constructors are not inherited, so override is meaningless\n- E07080: default constructor with parameters — default means no parameters\n\nSee Q89 for constructors. See Q768 for abstract methods. See Q86 for abstract classes.","ek9Example":"defines module qa.classesandoop.constructorabstract\n\n  defines class\n\n    Shape as abstract\n      name <- String()\n\n      Shape()\n        -> n as String\n        name :=: n\n\n      area() as abstract\n        <- rtn as Float?\n\n      describe()\n        <- rtn as String: name\n\n      default operator ?\n\n    Circle extends Shape\n      radius <- Float()\n\n      Circle()\n        -> r as Float\n        super(\"circle\")\n        radius :=: r\n\n      override area()\n        <- rtn as Float: radius * radius * 3.14159\n\n      default operator ?\n\n  defines program\n\n    ShowShapes()\n      stdout <- Stdout()\n      circle <- Circle(5.0)\n      if circle?\n        stdout.println(circle.describe())","migrationContext":"Java: abstract constructors are a compile error. Python: __init__ can be overridden but not marked abstract via abc. Rust: no constructors, new() is a convention. Kotlin: constructors cannot be abstract. Go: no constructors, factory functions used. EK9: constructors must be concrete, use abstract factory methods for polymorphic creation.","keywords":["E07050","abstract","class","constructor","create","default","factory","impossible","instance","override"],"primaryTopics":["constructors","E07050","abstract rules"],"typicalErrors":[{"error":"E07050","correct":"      Shape()\n        -> n as String\n        name :=: n","incorrect":"      Shape() as abstract\n        -> n as String","explanation":"Making the constructor abstract (no body) is logically impossible — constructors must create instances. A constructor cannot defer creation to a subclass. Remove 'as abstract' and provide a body. See ek9 -h E07050 for details."},{"error":"E07050","correct":"      Circle()\n        -> r as Float\n        super(\"circle\")\n        radius :=: r","incorrect":"      Circle() as abstract\n        -> r as Float","explanation":"A constructor cannot be abstract. It must create an instance of its class. Remove 'as abstract' and provide the constructor body. See ek9 -h E07050 for details."}],"companions":[]}
{"id":775,"category":"Classes and OOP","question":"Why can't a constructor use the override keyword in EK9?","url":"https://ek9.io/qa/QA0775.html","alternatePhrasings":["What triggers E07060 override constructor?","Are constructors inherited in EK9?","Why does override on a constructor fail?"],"answer":"Constructors are NOT inherited in EK9. Each class defines its own constructors independently. The 'override' keyword means 'I am replacing a method inherited from my parent' — since constructors are never inherited, override is meaningless on them.\n\nWHY CONSTRUCTORS ARE NOT INHERITED\nA constructor creates an instance of a specific class. ChildClass() creates a ChildClass, not a ParentClass. There is nothing to override because the parent's constructor belongs to the parent.\n\nCALLING PARENT CONSTRUCTORS\nUse super() to call the parent constructor from within your constructor. This is delegation, not overriding:\n  ChildClass()\n    -> name as String\n    super(name)\n\nTHIS EXAMPLE\nThe Animal class has a constructor taking a name. Dog extends Animal and has its own constructor that calls super() to initialise the parent. No override needed.\n\nSee Q774 for abstract constructor rules. See Q89 for constructor basics. See Q77 for inheritance.","ek9Example":"defines module qa.classesandoop.constructoroverride\n\n  defines class\n\n    Animal as open\n      name <- String()\n\n      Animal()\n        -> n as String\n        name :=: n\n\n      speak()\n        <- rtn as String: name\n\n      default operator ?\n\n    Dog extends Animal\n\n      Dog()\n        -> n as String\n        super(n)\n\n      override speak()\n        <- rtn as String: `${super.speak()} says woof`\n\n      default operator ?\n\n  defines program\n\n    ShowAnimals()\n      stdout <- Stdout()\n      dog <- Dog(\"Rex\")\n      if dog?\n        stdout.println(dog.speak())","migrationContext":"Java: constructors are not inherited, override annotation on constructor is compile error. Python: __init__ can be overridden (it is a regular method). Rust: no constructors to override, new() is a convention. Kotlin: constructors not inherited, override not applicable. Go: no constructors. EK9: constructors not inherited, override keyword rejected with E07060.","keywords":["E07060","child","class","constructor","extend","inherited","override","parent","super"],"primaryTopics":["constructors","E07060","inheritance"],"typicalErrors":[{"error":"E07060","correct":"      Dog()\n        -> n as String\n        super(n)","incorrect":"      override Dog()\n        -> n as String\n        super(n)","explanation":"Adding 'override' to a constructor is invalid — constructors are not inherited, so there is nothing to override. Each class defines its own constructors. Use super() to call the parent constructor. See ek9 -h E07060 for details."}],"companions":[]}
{"id":776,"category":"Classes and OOP","question":"Why can't a default constructor have parameters in EK9?","url":"https://ek9.io/qa/QA0776.html","alternatePhrasings":["What triggers E07080 invalid default constructor?","What does the default keyword mean on a constructor?","How do I auto-generate a constructor in EK9?"],"answer":"The 'default' keyword on a constructor means 'compiler, generate a zero-argument constructor that initialises all fields to their declared values'. Adding parameters contradicts this — the compiler cannot know how to map parameters to fields.\n\nDEFAULT CONSTRUCTOR RULES\n- 'default ClassName()' — no parameters, compiler generates body\n- All fields must be initialised at declaration for default to work\n- You can have BOTH a default constructor AND parameterised constructors\n\nTHIS EXAMPLE\nThe Config class has two fields initialised at declaration and uses 'default Config()' for zero-argument construction. It also has a parameterised constructor for custom values. Both coexist.\n\nWHEN TO USE DEFAULT\nUse default when all fields have sensible initial values and you want a zero-argument constructor without writing the body. Use a regular constructor when you need parameters.\n\nSee Q89 for constructor basics. See Q82 for default operators. See Q774 for abstract constructor rules.","ek9Example":"defines module qa.classesandoop.defaultconstructor\n\n  defines class\n\n    Config\n      name <- \"default\"\n      timeout <- 30\n\n      default Config()\n\n      Config()\n        ->\n          n as String\n          t as Integer\n        name :=: n\n        timeout :=: t\n\n      describe()\n        <- rtn as String: `${name} timeout=${timeout}`\n\n      default operator ?\n\n    Setting\n      label <- \"none\"\n      enabled <- false\n\n      default private Setting()\n\n      Setting()\n        -> l as String\n        label :=: l\n        enabled: true\n\n      default operator ?\n\n  defines program\n\n    ShowConfig()\n      stdout <- Stdout()\n      defaults <- Config()\n      custom <- Config(\"prod\", 60)\n      if defaults?\n        stdout.println(defaults.describe())\n      if custom?\n        stdout.println(custom.describe())","migrationContext":"Java: default constructor is implicit (no-arg, compiler-generated if no constructors defined). Python: __init__ with defaults provides similar pattern. Rust: Default trait derive provides zero-arg construction. Kotlin: default parameter values on primary constructor. Go: zero-value initialization is automatic. EK9: explicit 'default ClassName()' keyword, must be zero-arg, fields must be initialised.","keywords":["E07080","class","constructor","default","fields","generate","initialise","parameters","zero-arg"],"primaryTopics":["default constructor","E07080","auto-generation"],"typicalErrors":[{"error":"E07080","correct":"      default Config()","incorrect":"      default Config()\n        -> name as String","explanation":"Adding a parameter to a default constructor is invalid. The 'default' keyword means zero-argument auto-generated constructor. Either remove 'default' and write a parameterised constructor, or remove the parameter. See ek9 -h E07080 for details."},{"error":"E07080","correct":"      default private Setting()","incorrect":"      default private Setting()\n        -> initial as Integer","explanation":"Even with the private modifier, a default constructor cannot have parameters. Default means zero-arg. Remove 'default' to create a parameterised private constructor. See ek9 -h E07080 for details."}],"companions":[]}
{"id":777,"category":"Code Quality","question":"Why does EK9 reject discarded operator return values?","url":"https://ek9.io/qa/QA0777.html","alternatePhrasings":["What triggers E11050 discarded operator return?","Why must I capture the result of an operator?","What is the difference between += and + in EK9?"],"answer":"EK9 distinguishes between mutating operators (which change state) and computational operators (which return new values). If you call a computational operator and discard the result, the computation was pointless — dead code.\n\nMUTATING OPERATORS (return Void — side-effect IS the purpose)\n  price += tax      // Modifies price in place\n  count++           // Increments count\n  target :=: source // Copies source into target\n\nCOMPUTATIONAL OPERATORS (return values — the VALUE is the purpose)\n  total <- price + tax    // Must capture the result\n  isEqual <- a == b       // Must use the Boolean\n  len <- length name      // Must use the Integer\n\nTHIS EXAMPLE\nThe calculate() function captures the result of every operator. The mutation removes the capture, leaving a bare operator call that discards its result.\n\nSee Q238 for operator overview. See Q239 for comparison operators. See Q310 for code quality checks.","ek9Example":"defines module qa.codequality.discardedreturn\n\n  defines class\n\n    Calculator\n\n      add() as pure\n        ->\n          left as Float\n          right as Float\n        <- rtn as Float: left + right\n\n      multiply() as pure\n        ->\n          left as Float\n          right as Float\n        <- rtn as Float: left * right\n\n      default operator ?\n\n  defines program\n\n    ShowCalculation()\n      stdout <- Stdout()\n      calculator <- Calculator()\n      total <- calculator.add(100.0, 15.0)\n      stdout.println($total)","migrationContext":"Java: silently discards return values of any expression statement. Python: silently discards return values. Rust: warns about unused results with #[must_use]. Go: compile error for unused function returns but not operators. Kotlin: no enforcement. EK9: compile error for discarded computational operator returns — dead code is not allowed.","keywords":["E11050","capture","code","computational","dead","discarded","mutating","operator","result","return"],"primaryTopics":["discarded returns","E11050","dead code"],"typicalErrors":[{"error":"E11051","correct":"      total <- calculator.add(100.0, 15.0)\n      stdout.println($total)","incorrect":"      calculator.add(100.0, 15.0)\n      stdout.println(\"done\")","explanation":"Calling a pure method that returns a value and discarding the result is dead code. The method has no side effects, so without capturing the return value the call achieves nothing. See ek9 -h E11051 for details."}],"companions":[]}
{"id":778,"category":"Classes and OOP","question":"Why can't I change the access modifier when overriding a method?","url":"https://ek9.io/qa/QA0778.html","alternatePhrasings":["What triggers E05130 method access modifiers differ?","Can I make an overridden method private in EK9?","Why must override methods keep the same visibility?"],"answer":"When you override a method, the child version must have the same access level as the parent. A protected method in the parent must remain protected in the child. Changing it would break the contract.\n\nACCESS RULES FOR OVERRIDE\n- Public parent method: child must be public\n- Protected parent method: child must be protected\n- Private methods: cannot be overridden (not inherited)\n\nWHY THIS RULE EXISTS\nIf Parent.process() is protected and Child makes it private, code in sibling classes that calls process() would fail. EK9 catches this at compile time.\n\nTHIS EXAMPLE\nThe Formatter class has a protected helper() method. The PrefixFormatter overrides it with the correct protected access.\n\nSee Q77 for inheritance. See Q84 for open classes. See Q90 for field visibility.","ek9Example":"defines module qa.classesandoop.overrideaccess\n\n  defines class\n\n    Formatter as open\n      protected helper()\n        -> text as String\n        <- rtn as String: text\n\n      format()\n        -> message as String\n        <- rtn as String: helper(message)\n\n      default operator ?\n\n    PrefixFormatter extends Formatter\n\n      override protected helper()\n        -> text as String\n        <- rtn as String: `[PREFIX] ${text}`\n\n      default operator ?\n\n  defines program\n\n    ShowFormatter()\n      formatter <- PrefixFormatter()\n      if formatter?\n        stdout <- Stdout()\n        stdout.println(formatter.format(\"hello\"))","migrationContext":"Java: narrowing access on override is a compile error. Python: no access modifiers enforced. Rust: no method overriding. Kotlin: same as Java, cannot narrow access. Go: no method overriding. C#: override must match parent accessibility. EK9: same rule as Java/Kotlin/C#, compile-time enforcement.","keywords":["E05130","access","contract","method","modifier","override","private","protected","visibility"],"primaryTopics":["access modifiers","E05130","override rules"],"typicalErrors":[{"error":"E05130","correct":"      override protected helper()","incorrect":"      private helper()","explanation":"Changing the access from 'protected override' to 'private' narrows the visibility. The parent's helper() is protected, so the child must keep it protected. A private method with the same signature is treated as a different method, not an override — but EK9 detects this access mismatch. See ek9 -h E05130 for details."}],"companions":[]}
{"id":779,"category":"Classes and OOP","question":"Why must certain operators return a value in EK9?","url":"https://ek9.io/qa/QA0779.html","alternatePhrasings":["What triggers E07400 returning missing on an operator?","How do I define an operator with a return type?","Why does my comparison operator need a return declaration?"],"answer":"In EK9, computational operators (like <, ==, +, -, <>) must return a value. These operators are expressions — they compute something. Without a return declaration, the operator has no result and cannot be used in expressions.\n\nOPERATORS THAT MUST RETURN\n- Comparison: < must return Boolean\n- Equality: == must return Boolean\n- Arithmetic: +, - must return the same type\n- Spaceship: <=> must return Integer\n- Hash: #? must return Integer\n- String: $ must return String\n\nMUTATING OPERATORS (no return needed)\n- +=, -=, :=:, :~:, :^: modify in place, return Void\n- ++, -- increment/decrement in place\n- close releases resources\n\nTHIS EXAMPLE\nThe Temperature class defines < and == operators with correct return declarations. The mutation removes the return from the < operator.\n\nSee Q81 for operator overview. See Q239 for comparison operators. See Q82 for default operators.","ek9Example":"defines module qa.classesandoop.operatorreturn\n\n  defines class\n\n    Temperature\n      degrees <- Float()\n\n      Temperature()\n        -> d as Float\n        degrees :=: d\n\n      operator < as pure\n        -> other as Temperature\n        <- rtn as Boolean: degrees < other.degrees\n\n      operator == as pure\n        -> other as Temperature\n        <- rtn as Boolean: degrees == other.degrees\n\n      operator #? as pure\n        <- rtn as Integer: #? degrees\n\n      operator $ as pure\n        <- rtn as String: $degrees\n\n      default operator ?\n\n  defines program\n\n    CompareTemps()\n      stdout <- Stdout()\n      hot <- Temperature(35.0)\n      cold <- Temperature(5.0)\n      if cold < hot\n        stdout.println(\"cold is less than hot\")","migrationContext":"Java: operator overloading not supported (except + for String). Python: __lt__ must return a value, no enforcement on type. Rust: Ord trait methods have explicit return types. Kotlin: operator fun compareTo returns Int. Go: no operator overloading. EK9: operators have strict return type requirements enforced at compile time.","keywords":["E07400","boolean","comparison","declaration","missing","operator","return","type","value"],"primaryTopics":["operator return types","E07400","operators"],"typicalErrors":[{"error":"E07520","correct":"      operator < as pure\n        -> other as Temperature\n        <- rtn as Boolean: degrees < other.degrees","incorrect":"      operator < as pure\n        -> other as Temperature","explanation":"The < operator must return a Boolean but the return declaration is missing. Comparison operators have a specific return type requirement enforced by the compiler. See ek9 -h E07520 for details."},{"error":"E07550","correct":"      operator #? as pure\n        <- rtn as Integer: #? degrees","incorrect":"      operator #? as pure","explanation":"The #? (hashcode) operator must return an Integer but the return declaration is missing. See ek9 -h E07550 for details."},{"error":"E07570","correct":"      operator $ as pure\n        <- rtn as String: $degrees","incorrect":"      operator $ as pure","explanation":"The $ (string) operator must return a String but the return declaration is missing. See ek9 -h E07570 for details."},{"error":"E06280","correct":"      operator < as pure\n        -> other as Temperature\n        <- rtn as Boolean: degrees < other.degrees","incorrect":"      operator < as pure\n        ->\n          other as Temperature\n          extra as Integer\n        <- rtn as Boolean: degrees < other.degrees","explanation":"The < operator takes exactly one parameter but two were provided. Comparison operators must accept one argument of the type being compared. See ek9 -h E06280 for details."},{"error":"E06290","correct":"      operator == as pure\n        -> other as Temperature\n        <- rtn as Boolean: degrees == other.degrees","incorrect":"      operator == as pure\n        <- rtn as Boolean: true","explanation":"The == operator requires one parameter but none was provided. Comparison operators must accept one argument of the type being compared. See ek9 -h E06290 for details."}],"companions":[]}
{"id":780,"category":"Operators and Expressions","question":"Why can't I use ++ or -- in an expression in EK9?","url":"https://ek9.io/qa/QA0780.html","alternatePhrasings":["What triggers E07950 increment decrement expression not allowed?","Why is y <- x++ an error in EK9?","How do I increment a variable in EK9?"],"answer":"In EK9, ++ and -- are STATEMENT-ONLY operators. They modify a variable in place and return nothing. You cannot use them where a value is expected (assignments, conditions, function arguments).\n\nWHY STATEMENT-ONLY\nIn C/Java, 'y <- x++' has confusing semantics: y gets the OLD value before increment (C) or becomes an alias to x (Java). Both are bug sources. EK9 eliminates this by making ++ and -- void operations.\n\nCORRECT USAGE\n  count++         // OK — standalone statement\n  count--         // OK — standalone statement\n\nINCORRECT USAGE\n  y <- count++    // ERROR — ++ returns void, cannot assign\n  if count++      // ERROR — ++ returns void, not Boolean\n\nIF YOU NEED THE OLD VALUE\n  oldCount <- count\n  count++\n  // Now oldCount has the previous value\n\nSee Q238 for operator overview. See Q239 for comparison operators.","ek9Example":"defines module qa.operators.incrementstatement\n\n  defines program\n\n    CountItems()\n      stdout <- Stdout()\n      count <- 0\n\n      count++\n      count++\n      count++\n      total <- count\n\n      stdout.println($total)","migrationContext":"Java: x++ returns old value (post-increment), ++x returns new value (pre-increment). C/C++: same as Java plus undefined behavior in complex expressions. Python: no ++ or -- operators at all. Rust: no ++ or -- operators. Go: ++ and -- are statements only (same as EK9). Kotlin: ++ and -- work as expressions. EK9: ++ and -- are statements only, like Go.","keywords":["E07950","assign","bug","decrement","expression","increment","operator","statement","void"],"primaryTopics":["increment decrement","E07950","statement-only operators"],"typicalErrors":[{"error":"E07950","correct":"      count++\n      total <- count","incorrect":"      total <- count++","explanation":"Using ++ in an assignment expression is not allowed. The ++ operator is statement-only — it modifies the variable and returns void. Increment first, then assign separately. See ek9 -h E07950 for details."}],"companions":[]}
{"id":781,"category":"Classes and OOP","question":"Why does EK9 reject calls with the wrong number of arguments?","url":"https://ek9.io/qa/QA0781.html","alternatePhrasings":["What triggers E06280 too many arguments?","What triggers E06290 too few arguments?","How do I fix argument count mismatch in EK9?"],"answer":"EK9 enforces exact argument counts at compile time. A function or method defined with two parameters must be called with exactly two arguments — no more, no fewer.\n\nNO DEFAULT PARAMETERS\nUnlike Python or Kotlin, EK9 does not support default parameter values. Every parameter must be explicitly provided at the call site.\n\nNO VARARGS\nUnlike Java or Python, EK9 does not support variable argument lists. If you need flexible argument counts, use a List parameter.\n\nTHIS EXAMPLE\nThe Pair class constructor takes exactly two arguments. The combine() function takes exactly two Pair arguments.\n\nWHY STRICT COUNTS\n1. No ambiguity about which overload is called\n2. No hidden default values that change behaviour silently\n3. Every call site is explicit and self-documenting\n\nSee Q48 for why no default parameters. See Q49 for why no varargs. See Q89 for constructors.","ek9Example":"defines module qa.classesandoop.argumentcount\n\n  defines class\n\n    Pair\n      label <- String()\n      count <- Integer()\n\n      Pair()\n        ->\n          l as String\n          c as Integer\n        label :=: l\n        count :=: c\n\n      describe()\n        <- rtn as String: `${label}=${count}`\n\n      default operator ?\n\n  defines program\n\n    ShowPair()\n      stdout <- Stdout()\n      pair <- Pair(\"hello\", 42)\n      if pair?\n        stdout.println(pair.describe())","migrationContext":"Java: method overloading provides multiple argument counts, varargs with '...'. Python: default values, *args, **kwargs. Rust: no default params, no varargs. Kotlin: default parameter values, varargs. Go: no default params, variadic with '...'. EK9: no defaults, no varargs, strict argument count enforcement.","keywords":["E06280","E06290","arguments","constructor","count","function","mismatch","parameters","too few","too many"],"primaryTopics":["argument count","E06280","E06290"],"typicalErrors":[{"error":"E50060","correct":"      pair <- Pair(\"hello\", 42)","incorrect":"      pair <- Pair(\"hello\")","explanation":"The Pair constructor requires two arguments (String and Integer) but only one was provided. The compiler cannot resolve a Pair(String) constructor. Provide all required arguments. See ek9 -h E50060 for details."},{"error":"E50060","correct":"      pair <- Pair(\"hello\", 42)","incorrect":"      pair <- Pair(\"hello\", 42, true)","explanation":"The Pair constructor requires two arguments but three were provided. The compiler cannot resolve a Pair(String, Integer, Boolean) constructor. Remove the extra argument. See ek9 -h E50060 for details."}],"companions":[]}
{"id":782,"category":"Control Flow","question":"When does a switch need a returning block in EK9?","url":"https://ek9.io/qa/QA0782.html","alternatePhrasings":["What triggers E07405 returning required?","What triggers E07406 returning not required?","How do I use switch as an expression in EK9?"],"answer":"EK9 switch can be used as a STATEMENT (no return) or as an EXPRESSION (returns a value). The compiler enforces consistency:\n\nSWITCH AS STATEMENT\n  switch category\n    case \"A\"\n      stdout.println(\"Category A\")\n    default\n      stdout.println(\"Other\")\nNo '<-' return block. The switch just executes side effects.\n\nSWITCH AS EXPRESSION\n  label <- switch category\n    <- rtn as String: String()\n    case \"A\"\n      rtn: \"Alpha\"\n    default\n      rtn: \"Unknown\"\nThe '<- rtn as String' declares the return. Each case assigns to rtn.\n\nRULES\n- E07405: Using switch in assignment context WITHOUT a return block\n- E07406: Adding a return block to a switch NOT in assignment context\n\nTHIS EXAMPLE\nThe classify() function uses switch as an expression with a correct return block. The showCategory program uses switch as a statement without a return block.\n\nSee Q63 for switch basics. See Q68 for switch as expression. See Q80 for loop expressions.","ek9Example":"defines module qa.controlflow.switchexpression\n\n  defines function\n\n    classify() as pure\n      -> category as String\n      <- label as String?\n\n      label: switch category\n        <- rtn as String: String()\n        case \"A\"\n          rtn: \"Alpha\"\n        case \"B\"\n          rtn: \"Beta\"\n        default\n          rtn: \"Unknown\"\n\n  defines program\n\n    ShowCategories()\n      stdout <- Stdout()\n\n      result <- classify(\"A\")\n      stdout.println(result)\n\n      category <- \"B\"\n      switch category\n        case \"A\"\n          stdout.println(\"Alpha\")\n        case \"B\"\n          stdout.println(\"Beta\")\n        default\n          stdout.println(\"Unknown\")","migrationContext":"Java: switch expressions (Java 14+) use -> and yield. Python: match/case (3.10+) cannot return values directly. Rust: match is always an expression. Kotlin: when is always an expression. Go: switch is statement-only. EK9: switch can be either, compiler enforces correct form.","keywords":["E07405","E07406","assign","block","expression","returning","statement","switch","value"],"primaryTopics":["switch expression","E07405","E07406"],"typicalErrors":[{"error":"E07405","correct":"      label: switch category\n        <- rtn as String: String()\n        case \"A\"\n          rtn: \"Alpha\"\n        case \"B\"\n          rtn: \"Beta\"\n        default\n          rtn: \"Unknown\"","incorrect":"      label: switch category\n        case \"A\"\n          label: \"Alpha\"\n        case \"B\"\n          label: \"Beta\"\n        default\n          label: \"Unknown\"","explanation":"A switch used in an assignment context ('label: switch ...') requires a returning block ('<- rtn as String'); without it the switch produces no value to assign. See ek9 -h E07405 for details."},{"error":"E07406","correct":"      switch category\n        case \"A\"\n          stdout.println(\"Alpha\")","incorrect":"      switch category\n        <- rtn as String: String()\n        case \"A\"\n          stdout.println(\"Alpha\")","explanation":"Adding a returning block to a switch that is not in an assignment context is pointless — the return value would be discarded. Remove the '<- rtn' declaration. See ek9 -h E07406 for details."}],"companions":[]}
{"id":783,"category":"Classes and OOP","question":"Why does EK9 reject override when there is no parent method?","url":"https://ek9.io/qa/QA0783.html","alternatePhrasings":["What triggers E05110 does not override?","Why does my override keyword cause an error?","How do I correctly override a parent method in EK9?"],"answer":"Note: override applies to both methods and operators (e.g., override operator ? as pure, override operator $ as pure). \n\nThe 'override' keyword in EK9 means 'I am replacing an inherited method from my parent'. If no matching method exists in the parent, the compiler rejects it — you cannot override something that does not exist.\n\nCOMMON CAUSES\n1. Typo in method name — parent has 'process', child says 'override proccess'\n2. Wrong parameter types — parent takes String, child takes Integer\n3. Method is private in parent — private methods are not inherited\n4. Method does not exist in parent — wrong assumption about inheritance\n\nTHIS EXAMPLE\nThe Logger class has a log() method. The FileLogger overrides it correctly. The format() method is new to FileLogger (no override needed).\n\nWHY THIS MATTERS\nThe override keyword prevents silent shadowing. Without it, a method with the same name as a parent method would hide the parent version — a common source of bugs. EK9 requires explicit intent.\n\nSee Q77 for inheritance. See Q775 for constructor override rules. See Q778 for access modifier rules.","ek9Example":"defines module qa.classesandoop.doesnotoverride\n\n  defines class\n\n    Logger as open\n      log()\n        -> message as String\n        stdout <- Stdout()\n        stdout.println(message)\n\n      default operator ?\n\n    FileLogger extends Logger\n\n      override log()\n        -> message as String\n        stdout <- Stdout()\n        stdout.println(`[FILE] ${message}`)\n\n      format()\n        -> message as String\n        <- rtn as String: `[FILE] ${message}`\n\n      default operator ?\n\n  defines program\n\n    ShowLogger()\n      logger <- FileLogger()\n      if logger?\n        logger.log(\"hello\")\n        stdout <- Stdout()\n        stdout.println(logger.format(\"formatted\"))","migrationContext":"Java: @Override annotation is optional but recommended. Python: no override concept, methods silently shadow. Rust: no override, trait implementations are explicit. Kotlin: override keyword required (same as EK9). Go: no override, methods on embedded structs can be shadowed. EK9: override keyword mandatory when replacing inherited methods.","keywords":["E05110","inherit","match","method","override","parent","shadow","signature","typo"],"primaryTopics":["override keyword","E05110","method inheritance"],"typicalErrors":[{"error":"E05110","correct":"      format()\n        -> message as String\n        <- rtn as String: `[FILE] ${message}`","incorrect":"      override format()\n        -> message as String\n        <- rtn as String: `[FILE] ${message}`","explanation":"The format() method does not exist in the parent Logger class, so 'override' is incorrect. The method is new to FileLogger — remove the override keyword. See ek9 -h E05110 for details."},{"error":"E05110","correct":"      override log()\n        -> message as String","incorrect":"      override logging()\n        -> message as String","explanation":"The parent has log() but the child says 'override logging()' — the method name does not match any parent method. Fix the typo to match the parent's method name. See ek9 -h E05110 for details."}],"companions":[]}
{"id":784,"category":"Classes and OOP","question":"Why can't I extend a class in EK9?","url":"https://ek9.io/qa/QA0784.html","alternatePhrasings":["What triggers E05030 not open to extension?","Why are EK9 classes closed by default?","How do I make a class extensible in EK9?"],"answer":"EK9 classes are CLOSED by default — they cannot be extended unless explicitly marked 'as open'. This is the opposite of Java (where classes are open by default) and the same as Kotlin.\n\nWHY CLOSED BY DEFAULT\n1. Extending a class creates tight coupling between parent and child\n2. The parent must be designed for inheritance (fragile base class problem)\n3. Composition is almost always better than inheritance\n4. Built-in types (List, Dict, Optional) are deliberately closed\n\nHOW TO MAKE EXTENSIBLE\n  MyClass as open       // Now it can be extended\n  MyClass as abstract   // Abstract classes are implicitly open\n\nCOMPOSITION ALTERNATIVE\nInstead of extending a closed class, wrap it as a field:\n  MyWrapper\n    items as List of String\n    add(item as String)\n      items += item\n\nTHIS EXAMPLE\nThe Shape class is marked 'as open' so Circle can extend it. The Config class is closed — the program uses it directly.\n\nSee Q83 for why closed by default. See Q84 for the open modifier. See Q85 for composition.","ek9Example":"defines module qa.classesandoop.closedbydefault\n\n  defines class\n\n    Shape as open\n      name <- String()\n\n      Shape()\n        -> n as String\n        name :=: n\n\n      describe()\n        <- rtn as String: name\n\n      default operator ?\n\n    Circle extends Shape\n      radius <- Float()\n\n      Circle()\n        ->\n          n as String\n          r as Float\n        super(n)\n        radius :=: r\n\n      override describe()\n        <- rtn as String: `${super.describe()} r=${radius}`\n\n      default operator ?\n\n    ItemHolder\n      items <- List() of String\n\n      addItem()\n        -> item as String\n        items += item\n\n      count()\n        <- rtn as Integer: length items\n\n      default operator ?\n\n  defines program\n\n    ShowShapes()\n      stdout <- Stdout()\n      circle <- Circle(\"circle\", 5.0)\n      if circle?\n        stdout.println(circle.describe())\n\n      holder <- ItemHolder()\n      holder.addItem(\"one\")\n      holder.addItem(\"two\")\n      stdout.println($holder.count())","migrationContext":"Java: classes open by default, use 'final' to close. Python: classes always open. Rust: no class inheritance. Kotlin: classes closed by default, use 'open' keyword (same as EK9). Go: no class inheritance, composition only. C#: classes open by default, use 'sealed' to close. EK9: closed by default like Kotlin, 'as open' to allow extension.","keywords":["E05030","closed","composition","default","extends","final","inheritance","open","sealed"],"primaryTopics":["closed by default","E05030","inheritance design"],"typicalErrors":[{"error":"E05030","correct":"    Shape as open\n      name <- String()","incorrect":"    Shape\n      name <- String()","explanation":"Removing 'as open' makes Shape closed. Circle cannot extend a closed class. Either add 'as open' to Shape, or use composition instead of inheritance. See ek9 -h E05030 for details."},{"error":"E05030","correct":"      items <- List() of String\n\n      addItem()","incorrect":"    ItemList extends List of String\n\n      addItem()","explanation":"List is a built-in closed type — it cannot be extended. Use composition: hold a List as a field instead of inheriting from it. See ek9 -h E05030 for details."}],"companions":[]}
{"id":785,"category":"Data Flow Safety","question":"Why must variables be declared before use in EK9?","url":"https://ek9.io/qa/QA0785.html","alternatePhrasings":["What triggers E08010 used before defined?","Why does EK9 require declaration before use?","How do I fix a variable used before it is declared?"],"answer":"EK9 enforces strict top-to-bottom declaration order within a block. A variable must be declared before any line that references it. This prevents entire categories of bugs where variables are used with undefined or unexpected values.\n\nWHY DECLARATION BEFORE USE\n1. No undefined variable reads — every variable has a known value when first used\n2. No hoisting surprises — unlike JavaScript, variables don't 'float' to the top\n3. Clear data flow — reading top-to-bottom shows the complete picture\n\nTHIS EXAMPLE\nThe process() function declares greeting first, then uses it. The calculate() function declares both operands before using them in the result.\n\nCOMMON CAUSES\n1. Rearranging code without moving declarations\n2. Referencing a variable from a different scope\n3. Typo creating a new name instead of using existing variable\n\nSee Q22 for variable declarations. See Q19 for assignment operators.","ek9Example":"defines module qa.dataflowsafety.usedbeforedefined\n\n  defines program\n\n    ShowGreeting()\n      stdout <- Stdout()\n      greeting <- \"hello\"\n      stdout.println(greeting)\n\n      first <- 10\n      second <- 20\n      result <- first + second\n      stdout.println($result)","migrationContext":"Java: fields can be used before declaration in class scope, locals must be declared first. Python: no declaration needed, NameError at runtime if not assigned. Rust: must declare before use. Kotlin: must declare before use. Go: must declare before use. JavaScript: var is hoisted (surprising), let/const must be declared first. EK9: strict declaration-before-use in all scopes.","keywords":["E08010","before","declare","defined","hoisting","order","scope","use","variable"],"primaryTopics":["declaration order","E08010","data flow safety"],"typicalErrors":[{"error":"E08010","correct":"      greeting <- \"hello\"\n      stdout.println(greeting)","incorrect":"      stdout.println(greeting)\n      greeting <- \"hello\"","explanation":"The variable 'greeting' is used on the println line before it is declared on the next line. Move the declaration before the first use. See ek9 -h E08010 for details."}],"companions":[]}
{"id":786,"category":"Getting Started","question":"Which keywords from other languages are excluded from EK9?","url":"https://ek9.io/qa/QA0786.html","alternatePhrasings":["Why does EK9 reject goto, def, elif, None, and self?","What triggers E01076 goto not supported?","Why does my Python-style code fail in EK9?"],"answer":"EK9 deliberately excludes several keywords from other languages. These are not missing features — they were designed out of existence based on decades of evidence.\n\nEXCLUDED KEYWORDS\n- goto (E01076): Creates spaghetti code. Apple SSL bug (2014) caused by goto.\n- def (E01077): Python keyword. EK9 uses indentation-based declaration.\n- elif (E01078): Python keyword. EK9 uses 'else if' (two words).\n- None (E01079): Python keyword. EK9 uses tri-state (unset, not null/None).\n- self (E01080): Python keyword. EK9 uses 'this' for instance reference.\n- semicolons (E01074): Not needed. EK9 uses newlines as statement terminators.\n\nALSO EXCLUDED (not keywords but designed out)\n- break, continue, return: See Q84-Q85 for why and alternatives.\n- null: EK9 uses tri-state semantics instead.\n\nTHIS EXAMPLE\nShows correct EK9 patterns that replace these excluded constructs.\n\nSee Q274-Q281 for common AI mistakes. See Q84 for why no break/continue. See Q85 for why no return.","ek9Example":"defines module qa.gettingstarted.excludedkeywords\n\n  defines program\n\n    GradeChecker()\n      -> score as Integer\n      stdout <- Stdout()\n\n      excellentThreshold <- 90\n      goodThreshold <- 60\n\n      if score > excellentThreshold\n        stdout.println(\"Excellent\")\n      else if score > goodThreshold\n        stdout.println(\"Good\")\n      else\n        stdout.println(\"Needs work\")\n\n      stdout.println(\"Done\")","migrationContext":"Python: def, elif, None, self are core keywords. Java: goto is reserved but unused, semicolons required. C/C++: goto exists, semicolons required. Rust: no goto, no null (Option instead). Go: goto exists but discouraged, no semicolons needed. EK9: all excluded at the grammar level — compiler rejects them with specific error messages.","keywords":["E01076","E01077","E01078","E01079","E01080","None","def","elif","excluded","goto","keyword","self","semicolon"],"primaryTopics":["excluded keywords","language design","migration"],"typicalErrors":[{"error":"E01078","correct":"      goodThreshold <- 60","incorrect":"      elif <- 60","explanation":"The name 'elif' is an excluded keyword from Python and cannot be used as an identifier in EK9. Use 'else if' for chained conditions, and choose a different variable name. See ek9 -h E01078 for details."},{"error":"E01074","correct":"      stdout.println(\"Done\")","incorrect":"      stdout.println(\"Done\");","explanation":"EK9 does not use semicolons. Newlines are statement terminators. Remove the semicolon. See ek9 -h E01074 for details."}],"companions":[]}
{"id":787,"category":"Classes and OOP","question":"Why can't I use 'this' in a function in EK9?","url":"https://ek9.io/qa/QA0787.html","alternatePhrasings":["What triggers E05070 inappropriate use of this?","Where is the this keyword valid in EK9?","Why does my function reject this.field?"],"answer":"The 'this' keyword refers to the current object instance. It only exists inside class methods, constructors, and operators — places where an object instance is available.\n\nWHERE THIS IS VALID\n- Class methods and operators (refers to the class instance)\n- Record operators (refers to the record instance)\n- Component methods (refers to the component instance)\n- Constructors (refers to the object being created)\n\nWHERE THIS IS NOT VALID\n- Functions (stateless, no instance)\n- Programs (not an object)\n- Module-level code\n\nTHIS EXAMPLE\nThe Counter class uses this.count in its methods — valid because methods have an instance. The standalone increment() function has no instance, so it uses parameters instead.\n\nSee Q50 for functions vs methods. See Q93 for class basics.","ek9Example":"defines module qa.classesandoop.thisinkclasses\n\n  defines function\n\n    increment() as pure\n      -> current as Integer\n      <- rtn as Integer: current + 1\n\n  defines class\n\n    Counter\n      count <- Integer()\n\n      Counter()\n        -> initial as Integer\n        count :=: initial\n\n      next()\n        count: this.count + 1\n\n      current()\n        <- rtn as Integer: this.count\n\n      default operator ?\n\n  defines program\n\n    ShowCounter()\n      stdout <- Stdout()\n      counter <- Counter(0)\n      counter.next()\n      counter.next()\n      stdout.println($counter.current())\n      result <- increment(10)\n      stdout.println($result)","migrationContext":"Java: this available in all instance methods, not in static methods. Python: self is an explicit parameter (same concept). Rust: self/&self parameter in impl methods. Kotlin: this available in class members. Go: receiver parameter serves as this. EK9: this only in class/record/component methods and constructors, never in functions.","keywords":["E05070","class","function","instance","method","scope","stateless","this"],"primaryTopics":["this keyword","E05070","functions vs methods"],"typicalErrors":[{"error":"E05070","correct":"      <- rtn as Integer: current + 1","incorrect":"      <- rtn as Integer: current + 1\n      copy <- this()","explanation":"Using this() in a function attempts constructor delegation, but functions have no constructors. The this() call is only valid inside a class constructor. See ek9 -h E05070 for details."}],"companions":[]}
{"id":788,"category":"Control Flow","question":"Why does a switch expression require a default case in EK9?","url":"https://ek9.io/qa/QA0788.html","alternatePhrasings":["What triggers E07330 default required in switch expression?","Why must switch expressions be exhaustive in EK9?","How do I add a default case to a switch expression?"],"answer":"When switch is used as an EXPRESSION (capturing its result), every possible input must produce a value. A missing default means some inputs have no result — the compiler rejects this.\n\nSWITCH EXPRESSION (requires default)\n  label <- switch category\n    <- rtn as String: String()\n    case \"A\"\n      rtn: \"Alpha\"\n    default\n      rtn: \"Unknown\"\n\nSWITCH STATEMENT (default optional)\n  switch category\n    case \"A\"\n      stdout.println(\"Alpha\")\n\nEXCEPTION: ENUMERATED TYPES\nWhen switching on an enumeration and all values are covered, no default is needed — the compiler knows the match is exhaustive.\n\nTHIS EXAMPLE\nThe describe() function uses switch as expression with a default case. The mutation removes the default.\n\nSee Q63 for switch basics. See Q73 for exhaustive enum switch. See Q782 for switch expressions.","ek9Example":"defines module qa.controlflow.switchdefault\n\n  defines function\n\n    describe() as pure\n      -> category as String\n      <- label as String?\n\n      label: switch category\n        <- rtn as String: String()\n        case \"A\"\n          rtn: \"Alpha\"\n        case \"B\"\n          rtn: \"Beta\"\n        case \"C\"\n          rtn: \"Gamma\"\n        default\n          rtn: \"unknown category\"\n\n  defines program\n\n    ShowCategories()\n      stdout <- Stdout()\n      stdout.println(describe(\"A\"))\n      stdout.println(describe(\"Z\"))","migrationContext":"Java: switch expressions (14+) require exhaustive coverage or default. Python: match/case has no exhaustiveness check. Rust: match must be exhaustive (compiler enforced). Kotlin: when used as expression must be exhaustive. Go: switch statement only, no expression form. EK9: switch expression requires default unless enum is fully covered.","keywords":["E07330","case","default","exhaustive","expression","missing","required","switch"],"primaryTopics":["switch default","E07330","exhaustive matching"],"typicalErrors":[{"error":"E07330","correct":"        default\n          rtn: \"unknown category\"","incorrect":"        //no default case","explanation":"A switch expression must handle all possible inputs. Without a default case, some values of category would produce no result. Add a default case to handle unmatched values. See ek9 -h E07330 for details."}],"companions":[]}
{"id":789,"category":"Data Flow Safety","question":"Why does EK9 reject unused variables?","url":"https://ek9.io/qa/QA0789.html","alternatePhrasings":["What triggers E08090 not referenced?","How do I fix an unused variable error in EK9?","Why must every variable be used after declaration?"],"answer":"EK9 requires that every declared variable is used after its declaration. An unused variable is dead code — it was either left over from refactoring, or the developer forgot to use it.\n\nWHY UNUSED VARIABLES ARE ERRORS\n1. Dead code obscures intent — readers wonder why the variable exists\n2. Refactoring remnants — code was changed but cleanup was incomplete\n3. Possible bug — the developer meant to use it but used a different name\n\nTHIS EXAMPLE\nThe greet() function declares greeting and uses it in the return. Every variable serves a purpose.\n\nCOMMON FIX\n1. Remove the unused variable if it is not needed\n2. Use it in subsequent code if it was accidentally forgotten\n3. If the value is needed for side effects only, assign to a used variable\n\nSee Q785 for declaration order. See Q22 for variable declarations.","ek9Example":"defines module qa.dataflowsafety.unusedvariable\n\n  defines function\n\n    greet() as pure\n      -> name as String\n      <- rtn as String?\n\n      greeting <- \"hello\"\n      rtn: `${greeting} ${name}`\n\n  defines program\n\n    ShowGreeting()\n      stdout <- Stdout()\n      stdout.println(greet(\"world\"))","migrationContext":"Java: unused local variables are warnings, not errors. Python: no enforcement, linters can detect. Rust: warnings for unused variables, prefix with _ to suppress. Kotlin: warnings only. Go: unused variables are compile errors (same as EK9). EK9: unused variables are compile errors, like Go.","keywords":["E08090","code","dead","declared","refactoring","referenced","unused","variable"],"primaryTopics":["unused variables","E08090","dead code"],"typicalErrors":[{"error":"E08090","correct":"      greeting <- \"hello\"","incorrect":"      greeting <- \"hello\"\n      unused <- \"forgotten\"","explanation":"The variable 'unused' is declared but never referenced after assignment. Remove it or use it. See ek9 -h E08090 for details."}],"companions":[]}
{"id":790,"category":"Purity Contracts","question":"Why can't I reassign variables in a pure function?","url":"https://ek9.io/qa/QA0790.html","alternatePhrasings":["What triggers E08100 no pure reassignment?","What is allowed in a pure context in EK9?","How do I write pure functions without reassignment?"],"answer":"In EK9, pure functions cannot reassign variables using ':='. This ensures pure functions have no side effects — they compute a result from their inputs without modifying state.\n\nWHAT IS ALLOWED IN PURE\n- Declaration with initialisation: 'result <- computation()'\n- Guarded assignment: 'value :=? fallback' (only assigns if unset)\n- Return variable initialisation: '<- rtn as Type: expression'\n\nWHAT IS NOT ALLOWED IN PURE\n- Reassignment: 'existing := newValue' (E08100)\n- Mutation operators: '+=', '-=', ':=:', '++', '--' (E08120)\n- Calling non-pure methods (E08130)\n\nTHIS EXAMPLE\nThe add() function is pure. It declares result with '<-' and returns it. No reassignment needed.\n\nWHY PURITY MATTERS\n1. Pure functions are thread-safe — no shared mutable state\n2. Results are cacheable — same inputs always produce same output\n3. Easier to test — no setup or teardown of state\n4. Compiler can optimise — knows no side effects occur\n\nSee Q43 for pure function basics. See Q560 for purity contracts.","ek9Example":"defines module qa.puritycontracts.noreassignment\n\n  defines function\n\n    add() as pure\n      ->\n        left as Integer\n        right as Integer\n      <- rtn as Integer?\n\n      total <- left + right\n      rtn: total\n\n  defines program\n\n    ShowAdd()\n      stdout <- Stdout()\n      result <- add(10, 20)\n      stdout.println($result)","migrationContext":"Java: no purity enforcement, rely on convention. Python: no purity concept. Rust: mut keyword controls mutability but no function-level purity. Kotlin: val prevents reassignment but no pure functions. Go: no purity concept. Haskell: all functions pure by default. EK9: explicit 'as pure' keyword with compiler enforcement.","keywords":["E08100","function","immutable","pure","reassignment","side effect","thread safe"],"primaryTopics":["purity","E08100","pure functions"],"typicalErrors":[{"error":"E08100","correct":"      total <- left + right","incorrect":"      total <- 0\n      total := left + right","explanation":"Reassigning 'total' with ':=' is not allowed in a pure function. Use declaration with initialisation '<-' instead. Pure functions cannot modify variables after declaration. See ek9 -h E08100 for details."},{"error":"E08120","correct":"      total <- left + right","incorrect":"      total <- left\n      total += right","explanation":"Using the mutation operator '+=' in a pure function is not allowed. Mutation operators modify state in place — pure functions must not mutate. Use '<- left + right' to compute the result in a single declaration. See ek9 -h E08120 for details."}],"companions":[]}
{"id":791,"category":"Purity Contracts","question":"Why can't I call a non-pure function from a pure function?","url":"https://ek9.io/qa/QA0791.html","alternatePhrasings":["What triggers E08130 none pure call in pure scope?","How do I fix impure call in pure context?","What functions can I call from a pure function?"],"answer":"A pure function can only call other pure functions and methods. Calling a non-pure function would allow side effects to leak into the pure context, breaking the purity guarantee.\n\nTHE PURITY CHAIN\nIf A is pure and calls B, then B must also be pure. If B calls C, then C must be pure too. The entire call chain from a pure function must be pure.\n\nTHIS EXAMPLE\nThe double() and quadruple() functions are both pure. quadruple() calls double() — valid because double() is also pure. The logResult() function is NOT pure (it uses Stdout). The mutation adds a call to logResult() inside quadruple().\n\nSee Q43 for pure function basics. See Q790 for reassignment in pure.","ek9Example":"defines module qa.puritycontracts.impurecall\n\n  defines function\n\n    double() as pure\n      -> input as Integer\n      <- rtn as Integer: input * 2\n\n    quadruple() as pure\n      -> input as Integer\n      <- rtn as Integer?\n\n      rtn: double(double(input))\n\n    logResult()\n      -> result as Integer\n      stdout <- Stdout()\n      stdout.println($result)\n\n  defines program\n\n    ShowPurity()\n      stdout <- Stdout()\n      result <- quadruple(5)\n      logResult(result)\n      stdout.println($result)","migrationContext":"Java: no purity enforcement. Python: no purity concept. Rust: no function-level purity, uses ownership for safety. Kotlin: no purity concept. Haskell: IO monad separates pure from impure. EK9: explicit 'as pure' with transitive enforcement — the entire call chain must be pure.","keywords":["E08130","call","chain","function","impure","pure","side effect"],"primaryTopics":["purity chain","E08130","pure calling"],"typicalErrors":[{"error":"E08130","correct":"      rtn: double(double(input))","incorrect":"      rtn: double(double(input))\n      logResult(rtn)","explanation":"Calling the non-pure function logResult() from a pure function is not allowed. Pure functions can only call other pure functions. See ek9 -h E08130 for details."}],"companions":[]}
{"id":792,"category":"Control Flow","question":"Why must a switch on an enumeration cover all values?","url":"https://ek9.io/qa/QA0792.html","alternatePhrasings":["What triggers E07310 not all enumerated values present in switch?","How do I make an exhaustive enum switch in EK9?","Do I need a default when switching on an enum?"],"answer":"A switch on an enumeration must do BOTH: cover every declared value AND provide a default. These are two separate requirements, enforced by two different errors — a default does NOT excuse you from listing the cases.\n\nWHY EVERY VALUE MUST BE COVERED (E07310)\nIf you add a new value to the enum later (e.g. PURPLE), the compiler flags every switch that doesn't handle it.\n\nWHY A DEFAULT IS ALSO REQUIRED (E07320 statement / E07330 expression)\nEK9 enumerations are tri-state like every other EK9 type: an enumeration variable can exist but be UNSET. 'Color()' is unset, and 'Color(\"Purple\")' is also unset because the text matches no declared value — EK9 returns an unset Color rather than throwing. No 'case' can ever match the unset state, so 'default' is the branch that handles it.\n\nThis is why the default does not weaken exhaustiveness: the cases stay exhaustive over the DECLARED values, and the default covers 'no value at all'.\n\nTHIS EXAMPLE\ndescribe() handles all three colours and has a default returning \"unset\" — the branch taken by describe(Color()).\n\nSee Q73 for exhaustive enum switch. See Q87 for enumerations. See Q788 for switch default required.","ek9Example":"defines module qa.controlflow.enumswitch\n\n  defines type\n\n    Color\n      Red,\n      Green,\n      Blue\n\n  defines function\n\n    describe() as pure\n      -> shade as Color\n      <- rtn as String: \"unknown\"\n\n      switch shade\n        case Color.Red\n          rtn: \"red\"\n        case Color.Green\n          rtn: \"green\"\n        case Color.Blue\n          rtn: \"blue\"\n        default\n          rtn: \"unset\"\n\n  defines program\n\n    ShowColors()\n      stdout <- Stdout()\n      stdout.println(describe(Color.Red))\n      stdout.println(describe(Color.Blue))\n\n      //An enumeration is tri-state, so a Color can exist while holding no value.\n      //No 'case' can match that - which is why 'default' is mandatory. Both print \"unset\".\n      stdout.println(describe(Color()))\n      stdout.println(describe(Color(\"Purple\")))","migrationContext":"Java: exhaustive switch on sealed types (Java 21+). Python: match/case has no exhaustiveness check. Rust: match on enum must be exhaustive (compiler enforced). Kotlin: when on sealed class must be exhaustive. Go: no exhaustiveness check on switch. EK9: compiler enforces all declared enum values covered AND a default — unlike Rust/Kotlin, an EK9 enumeration is tri-state and can be UNSET, and the default is the branch that handles that state.","keywords":["E07310","E07320","E07330","all values","case","default","enum","exhaustive","missing","switch","tri-state","unset"],"primaryTopics":["exhaustive enum switch","E07310","enumerations"],"typicalErrors":[{"error":"E07310","correct":"        case Color.Green\n          rtn: \"green\"\n        case Color.Blue\n          rtn: \"blue\"","incorrect":"        case Color.Green\n          rtn: \"green\"","explanation":"The switch on Color only handles Red and Green — Blue is missing, so E07310 fires. Add 'case Color.Blue'. Note the existing 'default' does NOT satisfy this: the default is separately mandatory (E07320/E07330) because it handles the UNSET enumeration state, not the missing case. See ek9 -h E07310 for details."}],"companions":[]}
{"id":793,"category":"Getting Started","question":"Why does EK9 reject None and self as identifiers?","url":"https://ek9.io/qa/QA0793.html","alternatePhrasings":["What triggers E01079 None not supported?","Why can't I use self in EK9 like Python?","What is E01080 self not supported?"],"answer":"EK9 deliberately excludes the Python keywords 'None' and 'self' from the language.\n\nNONE (E01079)\nPython uses 'None' as its null equivalent. EK9 uses tri-state semantics (absent/unset/set) instead. The '?' operator checks whether a value is set. There is no null or None concept in EK9.\n\nSELF (E01080)\nPython uses 'self' as the explicit instance reference. EK9 uses 'this' (implicit in most contexts), consistent with Java, C++, and JavaScript conventions.\n\nCORRECT PATTERNS\n- Check if set: 'if userName?' (not 'if userName == None')\n- Unset variable: 'userName <- String()' creates an unset String\n- Instance reference: 'this.fieldName' (not 'self.fieldName')\n\nSee Q786 for other excluded keywords. See Q39-Q41 for tri-state semantics.","ek9Example":"defines module qa.gettingstarted.noneself\n\n  defines program\n\n    TriStateDemo()\n      stdout <- Stdout()\n\n      userName <- String()\n      greeting <- \"Hello\"\n\n      if userName?\n        stdout.println(`${greeting} ${userName}`)\n      else\n        stdout.println(\"No user name set yet\")","migrationContext":"Python: None is the null equivalent, self is the instance reference. Java: null exists, this is the instance reference. Rust: no null (Option instead), no self keyword for instances. Kotlin: null with nullable types, this is implicit. Go: nil is the zero value, no self/this keyword. EK9: None and self are rejected at the grammar level with specific error messages guiding developers to EK9 alternatives.","keywords":["E01079","E01080","None","Python","excluded","isSet","keyword","self","tri-state"],"primaryTopics":["excluded keywords","None rejected","self rejected"],"typicalErrors":[{"error":"E01079","correct":"      userName <- String()","incorrect":"      None <- String()","explanation":"The identifier 'None' is an excluded Python keyword and cannot be used as a variable name in EK9. EK9 uses tri-state semantics (absent/unset/set) instead of None/null. See ek9 -h E01079 for details."},{"error":"E01080","correct":"      greeting <- \"Hello\"","incorrect":"      self <- \"Hello\"","explanation":"The identifier 'self' is an excluded Python keyword and cannot be used as a variable name in EK9. EK9 uses 'this' for instance references. See ek9 -h E01080 for details."}],"companions":[]}
{"id":794,"category":"Getting Started","question":"Why does EK9 care about operator placement (prefix vs suffix)?","url":"https://ek9.io/qa/QA0794.html","alternatePhrasings":["What triggers E01084 prefix operator wrong position?","What triggers E01085 suffix operator wrong position?","Why does 'list$' fail but '$list' works?"],"answer":"EK9 has strict rules about operator position. Some operators must come BEFORE the expression (prefix), and one operator must come AFTER (suffix).\n\nPREFIX OPERATORS (must come BEFORE)\n  $value       converts to String\n  $$value      converts to JSON\n  #? collection   gets length or hashcode\n  #< list      gets first element\n  #> list      gets last element\n  ~ number     negates or reverses\n  not flag     boolean negation\n  abs number   absolute value\n  sqrt number  square root\n  empty list   checks if empty\n  length name  gets length\n\nSUFFIX OPERATOR (must come AFTER)\n  value?       checks if value is set (isSet)\n\nCOMMON MISTAKES\n  list#<       WRONG: #< is prefix, use '#< list'\n  value$       WRONG: $ is prefix, use '$value'\n  ?value       WRONG: ? is suffix, use 'value?'\n\nSee Q25 for the promote operator. See Q39 for tri-state and isSet.","ek9Example":"defines module qa.gettingstarted.operatorplacement\n\n  defines program\n\n    OperatorPlacementDemo()\n      -> userName as String\n      stdout <- Stdout()\n\n      nameText <- $userName\n      stdout.println(nameText)\n\n      nameLength <- length userName\n      stdout.println(`Length: ${nameLength}`)\n\n      if userName?\n        stdout.println(\"User name is set\")","migrationContext":"Java: all operators are infix or prefix (++x, !flag). Python: not is prefix, no suffix operators. Rust: ! is prefix (boolean), ? is suffix (error propagation, similar to EK9). Kotlin: !! is suffix (non-null assertion), ! is prefix. Go: ! is prefix, no suffix operators. EK9: strict prefix/suffix distinction enforced at the grammar level.","keywords":["E01084","E01085","isSet","length","operator","position","prefix","string","suffix"],"primaryTopics":["operator placement","prefix operators","suffix operators"],"typicalErrors":[{"error":"E01084","correct":"      nameText <- $userName","incorrect":"      nameText <- userName$","explanation":"The '$' operator is a prefix operator and must come BEFORE the expression. Write '$userName' not 'userName$'. See ek9 -h E01084 for details."},{"error":"E01085","correct":"      if userName?","incorrect":"      if ?userName","explanation":"The '?' operator is a suffix operator and must come AFTER the expression. Write 'userName?' not '?userName'. See ek9 -h E01085 for details."}],"companions":[]}
{"id":795,"category":"Data Flow Safety","question":"What is a logical tautology and why does EK9 reject it?","url":"https://ek9.io/qa/QA0795.html","alternatePhrasings":["What triggers E08085 logical tautology?","Why does 'flag or not flag' cause a compiler error?","How does EK9 detect always-true logical expressions?"],"answer":"EK9 detects logical tautologies and contradictions at compile time (E08085). A tautology is an expression that is ALWAYS true regardless of operand values. A contradiction is ALWAYS false.\n\nTAUTOLOGY PATTERNS (always true)\n  flag or not flag        always true\n  not flag or flag        always true\n\nCONTRADICTION PATTERNS (always false)\n  flag and not flag       always false\n  not flag and flag       always false\n\nWHY REJECTED\nThese expressions indicate logic errors - typically a copy-paste mistake or incorrect variable name. If always-true is intended, use 'true' directly. If always-false, use 'false'.\n\nCORRECT ALTERNATIVES\n  ready or fallback          different variables - valid\n  enabled and not expired    different variables - valid\n\nSee Q632 for define-before-use. See Q789 for unused variables.","ek9Example":"defines module qa.dataflowsafety.logicaltautology\n\n  defines function\n\n    checkAvailability()\n      ->\n        isReady as Boolean\n        hasFallback as Boolean\n      <- result as Boolean?\n\n      result: isReady or hasFallback\n\n    checkRequirements()\n      ->\n        isEnabled as Boolean\n        isExpired as Boolean\n      <- result as Boolean?\n\n      result: isEnabled and not isExpired","migrationContext":"Java: no compiler detection, relies on FindBugs/SpotBugs. Python: no detection, relies on pylint. Rust: clippy warns about tautological comparisons. Kotlin: IntelliJ warns but compiles. Go: go vet does not detect logical tautologies. EK9: compile-time error E08085, code cannot proceed past this check.","keywords":["E08085","always","code","contradiction","dead","false","logical","tautology","true"],"primaryTopics":["logical tautology","E08085","dead code detection"],"typicalErrors":[{"error":"E08085","correct":"      result: isReady or hasFallback","incorrect":"      result: isReady or not isReady","explanation":"The expression 'isReady or not isReady' is a logical tautology - it is always true regardless of the value of isReady. Use different variables or simplify to 'true'. See ek9 -h E08085 for details."}],"companions":[]}
{"id":796,"category":"Data Flow Safety","question":"Why does EK9 reject comparisons against variables with known constant values?","url":"https://ek9.io/qa/QA0796.html","alternatePhrasings":["What triggers E08086 condition always true?","What triggers E08087 condition always false?","How does EK9 track variable values through data flow?"],"answer":"EK9 tracks variable assignments through data flow analysis. When a variable is assigned a constant value and then compared, the compiler can determine whether the condition is always true or always false.\n\nALWAYS TRUE (E08086)\n  status <- \"active\"\n  if status == \"active\"   dead else branch\n\nALWAYS FALSE (E08087)\n  status <- \"active\"\n  if status == \"error\"    dead if body\n\nWHY REJECTED\nDead branches indicate logic errors. Either the condition is wrong, the variable should have a different value, or the check is unnecessary.\n\nCORRECT PATTERNS\n- Use parameters instead of constants when conditions should vary\n- Use the constant directly if one branch is never needed\n- Check a different variable that can actually change\n\nSee Q795 for logical tautology. See Q632 for define-before-use.","ek9Example":"defines module qa.dataflowsafety.conditionalways\n\n  defines function\n\n    lookupMode()\n      -> modeCode as Integer\n      <- rtn as String: String()\n\n      if modeCode > 0\n        rtn: \"active\"\n      else\n        rtn: \"inactive\"\n\n    lookupChannel()\n      -> channelId as Integer\n      <- rtn as String: String()\n\n      if channelId > 0\n        rtn: \"open\"\n      else\n        rtn: \"closed\"\n\n    checkMode()\n      -> modeCode as Integer\n      <- label as String: String()\n\n      currentMode <- lookupMode(modeCode)\n\n      if currentMode == \"active\"\n        label: \"System is active\"\n      else\n        label: \"System is inactive\"\n\n    checkChannel()\n      -> channelId as Integer\n      <- label as String: String()\n\n      channelState <- lookupChannel(channelId)\n\n      if channelState == \"closed\"\n        label: \"Channel closed\"\n      else\n        label: \"Channel available\"","migrationContext":"Java: no compiler detection, relies on FindBugs. Python: no detection. Rust: compiler does not warn for this pattern. Kotlin: IntelliJ may warn but compiles. Go: no detection. EK9: compile-time error E08086/E08087, prevents dead branches from reaching production.","keywords":["E08086","E08087","always","branch","condition","dead","false","flow","true"],"primaryTopics":["condition always true","condition always false","data flow analysis"],"typicalErrors":[{"error":"E08086","correct":"      currentMode <- lookupMode(modeCode)","incorrect":"      currentMode <- \"active\"","explanation":"When the compiler tracks that 'currentMode' was assigned the constant 'active', the condition 'currentMode == \"active\"' is always true, making the else branch unreachable dead code. Use a computed value instead. See ek9 -h E08086 for details."},{"error":"E08087","correct":"      channelState <- lookupChannel(channelId)","incorrect":"      channelState <- \"open\"","explanation":"When the compiler tracks that 'channelState' was assigned the constant 'open', the condition 'channelState == \"closed\"' is always false, making the if body unreachable dead code. Use a computed value instead. See ek9 -h E08087 for details."}],"companions":[]}
{"id":797,"category":"DI Validation","question":"How does EK9 detect circular component injection at compile time?","url":"https://ek9.io/qa/QA0797.html","alternatePhrasings":["What triggers E08190 DI circular dependency?","Why does mutual component injection fail to compile?","How do I break a circular dependency chain in EK9 DI?"],"answer":"EK9 detects circular dependency chains in component injection at compile time (E08190). When Component A depends on Component B, and Component B depends on Component A (directly or transitively), neither can be constructed first.\n\nCIRCULAR CHAIN\n  ComponentA -> needs AbstractB -> provided by ComponentB\n  ComponentB -> needs AbstractA -> provided by ComponentA\n  DEADLOCK: neither can be created first\n\nCOMPILE-TIME DETECTION\nUnlike Spring (runtime BeanCurrentlyInCreationException), EK9 catches circular dependencies before any code runs. The compiler analyses the full injection graph and rejects cycles.\n\nBREAKING CYCLES\n1. One-way dependency: only one component injects the other\n2. Extract shared logic into a third component\n3. Use an event or callback pattern instead of direct injection\n\nSee Q671 for the general circular dependency explanation. See Q673 for missing registration. See Q674 for duplicate registration.","ek9Example":"defines module qa.divalidation.circularcompiletime\n\n  defines component\n\n    Handler as abstract\n\n      handleEvent() as abstract\n        -> eventName as String\n        <- handled as Boolean?\n\n      default operator ?\n\n    NotificationHandler extends Handler\n\n      override handleEvent()\n        -> eventName as String\n        <- handled as Boolean: true\n\n      default operator ?\n\n    Dispatcher as abstract\n\n      dispatch() as abstract\n        -> eventName as String\n        <- dispatched as Boolean?\n\n      default operator ?\n\n    EventDispatcher extends Dispatcher\n\n      handler as Handler!\n\n      override dispatch()\n        -> eventName as String\n        <- dispatched as Boolean?\n\n        dispatched: handler.handleEvent(eventName)\n\n      default operator ?\n\n  defines application\n\n    OneWayApp\n      register NotificationHandler() as Handler\n      register EventDispatcher() as Dispatcher\n\n  defines program\n\n    CircularDiDemo() with application of OneWayApp\n      stdout <- Stdout()\n\n      dispatcher as Dispatcher!\n      sent <- dispatcher.dispatch(\"user-login\")\n      stdout.println(`Dispatched: ${sent}`)","migrationContext":"Java: Spring throws BeanCurrentlyInCreationException at runtime. Python: no DI framework detection. Go: Wire detects at code generation time. Rust: no standard DI. Kotlin: Koin throws at runtime. EK9: compile-time E08190 rejects circular chains before deployment.","keywords":["DI","E08190","circular","compile-time","component","cycle","deadlock","dependency","injection"],"primaryTopics":["circular dependency","E08190","DI compile-time validation"],"typicalErrors":[{"error":"E08190","correct":"    NotificationHandler extends Handler\n\n      override handleEvent()","incorrect":"    NotificationHandler extends Handler\n\n      dispatcher as Dispatcher!\n\n      override handleEvent()","explanation":"Injecting Dispatcher into NotificationHandler creates a circular dependency (Handler -> Dispatcher -> Handler) that the compiler rejects at compile time. See ek9 -h E08190 for details."}],"companions":[]}
{"id":798,"category":"DI Validation","question":"What happens with missing or duplicate component registrations?","url":"https://ek9.io/qa/QA0798.html","alternatePhrasings":["What triggers E08210 missing registration?","What triggers E08230 duplicate registration?","How does EK9 validate application registrations at compile time?"],"answer":"EK9 validates application registrations at compile time for two common errors.\n\nMISSING REGISTRATION (E08210)\nWhen a program injects a component type that has no matching registration in the linked application, the compiler raises E08210. Every injection point must have a 'register' declaration.\n\nDUPLICATE REGISTRATION (E08230)\nWhen an application registers two concrete implementations for the same abstract type, the compiler raises E08230. Each abstract type must have exactly ONE registration per application.\n\nCORRECT PATTERN\n- Register exactly one concrete implementation per abstract type\n- Use separate applications for different configurations\n- Ensure all transitive dependencies are registered\n\nSee Q673 for missing registration details. See Q674 for duplicate registration details. See Q671 for circular dependencies.","ek9Example":"defines module qa.divalidation.missingduplicate\n\n  defines component\n\n    CacheLayer as abstract\n\n      lookup() as abstract\n        -> cacheKey as String\n        <- cached as String?\n\n      default operator ?\n\n    ConcreteCache extends CacheLayer\n\n      override lookup()\n        -> cacheKey as String\n        <- cached as String: \"cached-\" + cacheKey\n\n      default operator ?\n\n    Repository as abstract\n\n      fetch() as abstract\n        -> recordId as String\n        <- fetched as String?\n\n      default operator ?\n\n    ConcreteRepo extends Repository\n\n      override fetch()\n        -> recordId as String\n        <- fetched as String: \"record-\" + recordId\n\n      default operator ?\n\n  defines application\n\n    CompleteConfig\n      register ConcreteCache() as CacheLayer\n      register ConcreteRepo() as Repository\n\n  defines program\n\n    RegistrationDemo() with application of CompleteConfig\n      stdout <- Stdout()\n\n      cacheService as CacheLayer!\n      repoService as Repository!\n\n      cachedItem <- cacheService.lookup(\"item-1\")\n      stdout.println(`Cache: ${cachedItem}`)\n\n      record <- repoService.fetch(\"rec-1\")\n      stdout.println(`Repo: ${record}`)","migrationContext":"Java: Spring throws NoSuchBeanDefinitionException (missing) or NoUniqueBeanDefinitionException (duplicate) at runtime. Python: runtime errors. Go: Wire detects at generation time. Rust: no standard DI. Kotlin: Koin throws at runtime. EK9: compile-time E08210/E08230 prevents both errors before deployment.","keywords":["DI","E08210","E08230","application","component","duplicate","inject","missing","registration"],"primaryTopics":["missing registration","duplicate registration","DI validation"],"typicalErrors":[{"error":"E08210","correct":"      register ConcreteCache() as CacheLayer\n      register ConcreteRepo() as Repository","incorrect":"      register ConcreteCache() as CacheLayer","explanation":"The program injects both CacheLayer and Repository, but only CacheLayer is registered. Every injection point must have a matching registration. Add 'register ConcreteRepo() as Repository' to the application. See ek9 -h E08210 for details."}],"companions":[]}
{"id":799,"category":"Code Quality","question":"Why does EK9 require named arguments when passing multiple Boolean literals?","url":"https://ek9.io/qa/QA0799.html","alternatePhrasings":["What triggers E11061 boolean arguments require names?","Why does 'connect(true, false)' fail to compile?","How do I pass multiple Boolean parameters in EK9?"],"answer":"When a call passes two or more Boolean literal values (true/false) as positional arguments, EK9 raises E11061. Boolean literals carry zero semantic signal at a call site.\n\nWHY AMBIGUOUS\n  connect(true, false)    What does each Boolean mean?\n  connect(true, true)     Completely opaque without checking the signature\n\nSINGLE BOOLEAN OK\n  setVisible(true)        Clear from context - one Boolean is tolerable\n\nFIX: USE NAMED ARGUMENTS\n  connect(useSsl: true, autoReconnect: false)\n  When naming any argument, EK9 requires ALL arguments to be named.\n\nSee Q777 for discarded operator returns. See Q310 for code quality overview.","ek9Example":"defines module qa.codequality.booleanargs\n\n  defines function\n\n    configureNetwork()\n      ->\n        hostName as String\n        useSsl as Boolean\n        autoReconnect as Boolean\n      <- summary as String: String()\n\n      sslLabel <- \"no-ssl\"\n      if useSsl\n        sslLabel: \"ssl\"\n\n      reconnectLabel <- \"no-reconnect\"\n      if autoReconnect\n        reconnectLabel: \"reconnect\"\n\n      summary: `${hostName} [${sslLabel}, ${reconnectLabel}]`\n\n  defines program\n\n    BooleanArgDemo()\n      stdout <- Stdout()\n\n      networkInfo <- configureNetwork(hostName: \"db.example.com\", useSsl: true, autoReconnect: false)\n      stdout.println(networkInfo)","migrationContext":"Java: silently allows positional Boolean arguments. Python: allows but style guides recommend kwargs. Swift: requires argument labels by default. Rust: no named arguments (use builder pattern). Kotlin: supports named arguments but doesn't require them. Go: no named arguments. EK9: compile-time error when 2+ Boolean literals appear as positional arguments.","keywords":["E11061","ambiguous","argument","boolean","call","named","positional","readability","site"],"primaryTopics":["boolean arguments","E11061","named arguments"],"typicalErrors":[{"error":"E11061","correct":"configureNetwork(hostName: \"db.example.com\", useSsl: true, autoReconnect: false)","incorrect":"      configureNetwork(\"db.example.com\", true, false)","explanation":"Passing two Boolean literals as positional arguments is ambiguous - the reader cannot tell what 'true, false' means without checking the function signature. Use named arguments for clarity. See ek9 -h E11061 for details."}],"companions":[]}
{"id":800,"category":"Code Quality","question":"Why does EK9 reject repeated literal values?","url":"https://ek9.io/qa/QA0800.html","alternatePhrasings":["What triggers E11065 repeated magic literal?","How many times can I use the same string literal?","Why must repeated values be extracted to constants?"],"answer":"EK9 raises E11065 when the same literal value appears 3 or more times in a file. Repeated literals are a maintenance liability.\n\nWHY REJECTED\nWhen a literal needs to change, every occurrence must be found and updated. Research (Bettenburg & Shang, 2010) found that ~50% of changes to duplicated code are inconsistent - only some instances get updated.\n\nTHRESHOLDS\n- 3+ occurrences in a single file: error\n- 4+ occurrences across files in the same module: error\n\nFIX: EXTRACT TO CONSTANT OR VARIABLE\n  //Before: repeated literal\n  stdout.println(\"processing\")\n  stdout.println(\"processing\")\n  stdout.println(\"processing\")\n\n  //After: named constant\n  statusMsg <- \"processing\"\n  stdout.println(statusMsg)\n  stdout.println(statusMsg)\n  stdout.println(statusMsg)\n\nSee Q799 for boolean argument naming. See Q777 for discarded returns.","ek9Example":"defines module qa.codequality.repeatedliteral\n\n  defines program\n\n    MagicLiteralDemo()\n      stdout <- Stdout()\n\n      statusMsg <- \"processing\"\n      stdout.println(statusMsg)\n      stdout.println(statusMsg)\n      stdout.println(statusMsg)","migrationContext":"Java: no compiler detection, relies on SonarQube S1192 or PMD. Python: no detection, relies on pylint. Rust: no detection for repeated literals. Kotlin: no compiler detection. Go: no detection. EK9: compile-time error E11065 at threshold of 3 per file or 4 per module.","keywords":["E11065","constant","duplication","extract","literal","magic","maintenance","repeated"],"primaryTopics":["repeated magic literal","E11065","named constants"],"typicalErrors":[{"error":"E11065","correct":"      statusMsg <- \"processing\"\n      stdout.println(statusMsg)\n      stdout.println(statusMsg)\n      stdout.println(statusMsg)","incorrect":"      stdout.println(\"processing\")\n      stdout.println(\"processing\")\n      stdout.println(\"processing\")","explanation":"The same literal 'processing' appears 3 times in the file. Extract it to a named variable or constant to avoid maintenance errors when the value needs to change. See ek9 -h E11065 for details."}],"companions":[]}
{"id":801,"category":"Code Quality","question":"Why does EK9 reject overrides identical to the parent method?","url":"https://ek9.io/qa/QA0801.html","alternatePhrasings":["What triggers E11044 redundant override?","Why can't I override a method with the same body?","What does redundant override mean in EK9?"],"answer":"EK9 raises E11044 when an override method body is textually identical to the method it overrides in the parent class. The override adds no value and duplicates inherited behaviour.\n\nWHY REJECTED\n- Copy-paste from the parent class without modification\n- Incomplete refactoring where the override was meant to change behaviour\n- Misunderstanding of inheritance (overrides are only needed to CHANGE behaviour)\n\nFIX OPTIONS\n1. Remove the override entirely - inherited implementation provides identical behaviour\n2. Actually modify the method body to add new behaviour\n3. Call super and add additional logic\n\nSee Q800 for repeated literals. See Q777 for discarded returns.","ek9Example":"defines module qa.codequality.redundantoverride\n\n  defines class\n\n    Formatter as open\n\n      format()\n        -> inputText as String\n        <- formatted as String: String()\n\n        formatted: `[${inputText}]`\n\n      default operator ?\n\n    EnhancedFormatter extends Formatter\n\n      override format()\n        -> inputText as String\n        <- formatted as String: String()\n\n        formatted: \"Enhanced: \" + inputText\n\n      default operator ?\n\n  defines program\n\n    RedundantOverrideDemo()\n      stdout <- Stdout()\n\n      formatter <- EnhancedFormatter()\n      styled <- formatter.format(\"hello\")\n      stdout.println(styled)","migrationContext":"Java: no compiler detection, IntelliJ warns. Python: no detection. Rust: no inheritance, uses trait composition. Kotlin: no detection, IntelliJ warns. Go: no inheritance. EK9: compile-time error E11044, redundant overrides cannot exist.","keywords":["E11044","copy","duplicate","identical","inherited","override","parent","paste","redundant"],"primaryTopics":["redundant override","E11044","inheritance"],"typicalErrors":[{"error":"E11044","correct":"        formatted: \"Enhanced: \" + inputText","incorrect":"        formatted: `[${inputText}]`","explanation":"The override body is textually identical to the parent Formatter.format() method. Either remove the override to inherit the parent behaviour, or change the body to provide genuinely different behaviour. See ek9 -h E11044 for details."}],"companions":[]}
{"id":802,"category":"Error Handling and Exceptions","question":"Why does EK9 reject manual close of local resources?","url":"https://ek9.io/qa/QA0802.html","alternatePhrasings":["What triggers E81055 manual close of local resource?","Why must I use try-with-resources for local variables?","How do I properly close resources in EK9?"],"answer":"EK9 raises E81055 when you manually close a locally-declared resource. Manual close is unsafe because if an exception occurs before the close statement, the resource leaks.\n\nWHY UNSAFE\n  resource <- MyResource(\"test\")\n  doWork()         //exception here?\n  close resource   //might never execute!\n\nResearch shows ~32% of resource leak bugs are caused by exceptions occurring before manual close() calls.\n\nCORRECT: TRY-WITH-RESOURCES\n  try\n    -> resource <- MyResource(\"test\")\n    doWork()\n  //close() GUARANTEED to be called\n\nWHAT IS ALLOWED\n- Closing a PARAMETER (caller transferred ownership)\n- Closing a FIELD (aggregate manages its own resources)\n- try-with-resources for local resources\n\nSee Q137 for try-with-resources syntax. See Q803 for abandoned resource detection.","ek9Example":"defines module qa.errorhandling.manualclose\n\n  defines class\n\n    DatabaseConn\n      connName <- String()\n\n      DatabaseConn()\n        -> connectionName as String\n        connName: connectionName\n\n      query()\n        -> queryText as String\n        <- queryResult as String: `${queryText} from ${connName}`\n\n      operator close as pure\n        require true\n\n      override operator ? as pure\n        <- rtn <- true\n\n  defines function\n\n    safeResourceUsage()\n      stdout <- Stdout()\n\n      try\n        -> conn <- DatabaseConn(\"test-db\")\n        queryResult <- conn.query(\"SELECT 1\")\n        stdout.println(queryResult)","migrationContext":"Java: allows manual close() but try-with-resources recommended since Java 7. Python: 'with' statement recommended but manual close allowed. Rust: RAII with Drop trait, automatic cleanup. Go: defer close() pattern, easy to forget. C#: using statement for IDisposable. EK9: compile-time error E81055, manual close of locals is impossible.","keywords":["E81055","close","leak","local","manual","resource","resources","try","with"],"primaryTopics":["manual close","E81055","resource management"],"typicalErrors":[{"error":"E81055","correct":"      try\n        -> conn <- DatabaseConn(\"test-db\")\n        queryResult <- conn.query(\"SELECT 1\")\n        stdout.println(queryResult)","incorrect":"      conn <- DatabaseConn(\"test-db\")\n      queryResult <- conn.query(\"SELECT 1\")\n      stdout.println(queryResult)\n      close conn","explanation":"Manual close of a locally-declared resource is unsafe. If an exception occurs before the close statement, the resource leaks. Use try-with-resources instead. See ek9 -h E81055 for details."}],"companions":[]}
{"id":803,"category":"Error Handling and Exceptions","question":"What happens when a closeable resource is never closed?","url":"https://ek9.io/qa/QA0803.html","alternatePhrasings":["What triggers E81056 resource never closed or escaped?","How does EK9 detect abandoned resources?","Why must closeable resources be closed or transferred?"],"answer":"EK9 raises E81056 when a variable with a close() operator is created but never closed and does not escape the current scope. This is a guaranteed resource leak.\n\nWHAT COUNTS AS ESCAPE\nA resource escapes (ownership transferred) when:\n- RETURNED: assigned to return variable\n- STORED: assigned to a field or collection\n- PASSED: given to another method as argument\n- WRAPPED: placed in Optional, Result, or container\n\nFIX OPTIONS\n1. try-with-resources: automatic close when scope ends (recommended)\n2. Return it: transfer ownership to the caller\n3. Pass it: give to another component for management\n4. Store it: place in a container for batch management\n\nResearch shows ~30% of resource leak bugs are caused by developers forgetting to close resources entirely.\n\nSee Q802 for manual close rejection. See Q137 for try-with-resources syntax.","ek9Example":"defines module qa.errorhandling.resourceneverclosed\n\n  defines class\n\n    FileConn\n      fileName <- String()\n\n      FileConn()\n        -> name as String\n        fileName: name\n\n      readAll()\n        <- content as String: \"contents of \" + fileName\n\n      operator close as pure\n        require true\n\n      override operator ? as pure\n        <- rtn <- true\n\n  defines function\n\n    safeFileRead()\n      stdout <- Stdout()\n\n      try\n        -> fileConn <- FileConn(\"log.txt\")\n        content <- fileConn.readAll()\n        stdout.println(content)","migrationContext":"Java: unclosed resources only detected by FindBugs/SpotBugs, not the compiler. Python: no detection, relies on linters. Rust: Drop trait guarantees cleanup. Go: no detection (defer is voluntary). C#: Roslyn CA2000 warns but code still compiles. EK9: compile-time error E81056, abandoned resources cannot exist.","keywords":["E81056","abandoned","close","escape","leak","ownership","resource","transfer","try"],"primaryTopics":["abandoned resource","E81056","resource leak detection"],"typicalErrors":[{"error":"E81056","correct":"      try\n        -> fileConn <- FileConn(\"log.txt\")\n        content <- fileConn.readAll()\n        stdout.println(content)","incorrect":"      fileConn <- FileConn(\"log.txt\")\n      content <- fileConn.readAll()\n      stdout.println(content)","explanation":"A closeable resource was created but never closed via try-with-resources and does not escape the scope. This is a guaranteed resource leak. Wrap it in try-with-resources. See ek9 -h E81056 for details."}],"companions":[]}
{"id":804,"category":"Testing","question":"Why does EK9 have a built-in test framework instead of using JUnit or pytest?","url":"https://ek9.io/qa/QA0804.html","alternatePhrasings":["How does EK9 testing compare to JUnit?","Why not use an external test framework with EK9?","What is the EK9 equivalent of JUnit or pytest?"],"answer":"EK9 builds testing into the compiler and language, eliminating the need for JUnit, pytest, or any external test framework. This is a deliberate design choice.\n\nWHY BUILT-IN\nExternal frameworks create a gap between what the compiler knows and what the test runner knows. EK9 closes that gap:\n- The compiler validates test quality at compile time (no empty tests, no orphan assertions)\n- Coverage instrumentation is built into the compilation pipeline\n- No dependency management for test libraries\n- No version conflicts between test framework and language\n\nCOMPARISON\nJava: JUnit 5 is a separate dependency, needs Maven/Gradle integration, assertion methods imported from org.junit.jupiter.api. Python: pytest is pip-installed, uses assert rewriting magic. Rust: #[test] is built-in but coverage requires external tools (tarpaulin). Go: testing package is built-in — closest to EK9's approach.\n\nEK9 goes further than Go: compile-time test quality validation (E81007 empty test, E81011 orphan assertion), built-in coverage with 80% threshold, HTML dashboard, and test grouping for parallel/sequential control.\n\nTHE QUALITY ADVANTAGE\nBecause the compiler sees your tests, it can enforce quality:\n- Tests must actually verify something (assert, assertThrows, or expected_output.txt)\n- Assertions must be reachable from a @Test program (call graph analysis)\n- Assertions cannot appear in production code\n- All code quality metrics (complexity, cohesion, coupling) apply to test code too\n\nNo external framework can provide this level of integration.\n\nSee Q155 for writing tests. See Q206 for coverage. See Q207 for output formats.","ek9Example":"defines module qa.testdeep.why.builtin\n\n  defines function\n\n    calculate() as pure\n      -> n as Integer\n      <- rtn as Integer: n * n\n\n  defines program\n\n    // === EK9: NO FRAMEWORK NEEDED ===\n    // No imports, no dependencies, no build plugin.\n    // Just @Test and assert.\n\n    @Test\n    SquareOfFiveTest()\n      result <- calculate(5)\n      assert result == 25\n\n    // === GROUPED TESTS RUN SEQUENTIALLY ===\n\n    @Test: \"math\"\n    SquareOfThreeTest()\n      result <- calculate(3)\n      assert result == 9\n\n    @Test: \"math\"\n    SquareOfZeroTest()\n      result <- calculate(0)\n      assert result == 0","migrationContext":"Java: JUnit 5 requires org.junit.jupiter dependency, @Test annotation, Assertions.* static imports, build tool plugin. Python: pytest requires pip install, conftest.py configuration, -v flags. Rust: #[test] is built-in but cargo-tarpaulin needed for coverage. Go: testing.T built-in, go test -cover for coverage. EK9: everything built in — @Test, assert, assertThrows, coverage, HTML reports, quality validation.","keywords":["advantage","built-in","comparison","coverage","framework","junit","migrate","pytest","quality","testing"],"primaryTopics":["junit equivalent","pytest equivalent","built-in testing"],"typicalErrors":[{"error":"E81007","correct":"    @Test\n    SquareOfFiveTest()\n      result <- calculate(5)\n      assert result == 25","incorrect":"@Test\n    EmptyTest()\n      result <- calculate(5)\n      stdout <- Stdout()\n      stdout.println($result)","explanation":"A @Test program must validate something: assert statements, assertThrows, or an expected_output.txt file. Printing output without a companion expected file is an empty test. See ek9 -h E81007 for details."}],"companions":[]}
{"id":805,"category":"Testing","question":"How do I organise tests relative to production code in EK9?","url":"https://ek9.io/qa/QA0805.html","alternatePhrasings":["Where do I put test files in an EK9 project?","What is the dev/ directory for in EK9?","How does EK9 separate test code from production code?"],"answer":"EK9 uses a dev/ directory convention to separate test code from production code. Tests live alongside the code they test, not in a separate tree.\n\nDIRECTORY STRUCTURE\nAn EK9 project typically has:\n  myProject/\n    myModule.ek9          <- production code\n    dev/\n      tests.ek9           <- test code for myModule\n      expected_output.txt  <- optional: for black-box tests\n\nTHE dev/ DIRECTORY\nFiles in dev/ are only compiled when running tests (ek9 -t). They are excluded from production builds. This means:\n- Test code never ships to production\n- Test utilities, helpers, and fixtures stay separate\n- No #ifdef or conditional compilation needed\n\nTESTS LIVE WITH THEIR CODE\nUnlike Java (src/test separate tree) or Python (tests/ directory), EK9 tests sit next to the code they test. This makes it easy to find tests and keeps related code together.\n\nMULTIPLE TEST FILES\nYou can have multiple test files in dev/:\n  myProject/\n    myModule.ek9\n    dev/\n      unitTests.ek9\n      integrationTests.ek9\n      expected_output.txt\n\nEXPECTED OUTPUT FILES\nFor black-box tests, put the expected output file alongside the test:\n- Single case: expected_output.txt\n- Multiple cases: expected_case_1.txt, expected_case_2.txt\n\nSee Q155 for writing tests. See Q204 for black-box tests. See Q205 for parameterized tests.","ek9Example":"defines module qa.testdeep.organisation\n\n  defines function\n\n    greet() as pure\n      -> name as String\n      <- rtn as String: `Hello, ${name}!`\n\n  defines program\n\n    // === PRODUCTION CODE + TEST STRUCTURE ===\n    // In a real project:\n    //   myApp/\n    //     greeting.ek9     <- this production code\n    //     dev/\n    //       tests.ek9      <- test code (only compiled with -t)\n\n    TestOrganisationDemo()\n      stdout <- Stdout()\n\n      // Production function being tested\n      message <- greet(\"EK9\")\n      stdout.println(message)\n\n      // Test programs would be in dev/tests.ek9:\n      //   @Test\n      //   GreetingTest()\n      //     result <- greet(\"World\")\n      //     assert result == \"Hello, World!\"\n\n      stdout.println(\"Tests live in dev/ directory\")\n      stdout.println(\"Excluded from production builds\")\n      stdout.println(\"Compiled only with ek9 -t\")","migrationContext":"Java: src/main/java and src/test/java separate trees, mirrored package structure. Python: tests/ directory or same package with test_ prefix. Rust: #[cfg(test)] mod tests in same file, or tests/ directory. Go: _test.go suffix in same package. EK9: dev/ directory alongside production code, excluded from production builds automatically.","keywords":["dev","directory","layout","organise","production","project","separate","structure","test"],"primaryTopics":["test organisation","dev directory","project structure"],"typicalErrors":[{"error":"E50010","correct":"      stdout <- Stdout()\n","incorrect":"stdout.println(\"test output\")\n      stdout <- Stdout()","explanation":"Variables must be declared before use. Stdout must be created before calling println on it. See ek9 -h E50010 for details."}],"companions":[]}
{"id":806,"category":"Testing","question":"How do I group tests and control execution order in EK9?","url":"https://ek9.io/qa/QA0806.html","alternatePhrasings":["What does @Test: \"groupname\" do in EK9?","How do I run tests sequentially in EK9?","How do I run a specific subset of tests in EK9?"],"answer":"EK9 test grouping controls whether tests run in parallel or sequentially, and lets you run subsets.\n\nUNGROUPED TESTS RUN IN PARALLEL\nTests without a group name run in parallel:\n  @Test\n  TestA()\n    assert 1 + 1 == 2\n  @Test\n  TestB()\n    assert 2 + 2 == 4\nTestA and TestB may run simultaneously on different threads.\n\nGROUPED TESTS RUN SEQUENTIALLY\nTests in the same group run one after another:\n  @Test: \"database\"\n  SetupDbTest()\n    // runs first\n  @Test: \"database\"\n  QueryDbTest()\n    // runs after SetupDbTest\nThis guarantees ordering within the group. Different groups still run in parallel.\n\nRUNNING A SPECIFIC GROUP\nUse -tg to run only tests in a named group:\n  ek9 -tg database myApp.ek9\nThis skips all tests not in the \"database\" group.\n\nLISTING TESTS WITHOUT RUNNING\nUse -tL to discover tests without executing them:\n  ek9 -tL myApp.ek9\nShows all test programs and their groups. Combine with -tg:\n  ek9 -tL -tg database myApp.ek9\n\nWHEN TO USE GROUPS\n- Tests that share state (database, files): group them sequentially\n- Tests that modify shared resources: group to avoid race conditions\n- Fast independent tests: leave ungrouped for parallel speed\n\nSee Q155 for basic testing. See Q207 for output formats. See Q804 for why built-in.","ek9Example":"defines module qa.testdeep.grouping\n\n  defines function\n\n    initializeState() as pure\n      <- rtn as String: \"ready\"\n\n    processItem() as pure\n      -> item as String\n      <- rtn as String: `processed: ${item}`\n\n  defines program\n\n    // === UNGROUPED: RUN IN PARALLEL ===\n\n    @Test\n    FastCheckA()\n      assert 1 + 1 == 2\n\n    @Test\n    FastCheckB()\n      assert 2 * 3 == 6\n\n    // === GROUPED: RUN SEQUENTIALLY ===\n\n    @Test: \"workflow\"\n    WorkflowStepOne()\n      state <- initializeState()\n      assert state == \"ready\"\n\n    @Test: \"workflow\"\n    WorkflowStepTwo()\n      result <- processItem(\"data\")\n      assert result == \"processed: data\"\n\n    // ek9 -tg workflow myApp.ek9  <- runs only \"workflow\" group\n    // ek9 -tL myApp.ek9           <- lists all tests and groups","migrationContext":"Java: JUnit @Tag for filtering, @TestMethodOrder for ordering, @Execution(SAME_THREAD) for sequential. Python: pytest -k for filtering, pytest-ordering for order control. Rust: #[test] runs in parallel by default, --test-threads=1 for sequential. Go: t.Run() subtests, -run regex filtering. EK9: @Test: \"group\" for sequential grouping, -tg for filtering, ungrouped tests run in parallel.","keywords":["execution","filter","group","list","order","parallel","sequential","subset","tL","tg"],"primaryTopics":["test grouping","test group","sequential tests"],"typicalErrors":[],"companions":[]}
{"id":807,"category":"Testing","question":"How do I run specific tests or list available tests in EK9?","url":"https://ek9.io/qa/QA0807.html","alternatePhrasings":["How do I filter which tests to run in EK9?","What does ek9 -tL do?","How do I run just one test group in EK9?"],"answer":"EK9 provides the -tg flag to filter by group and -tL to list tests without running them.\n\nRUN ALL TESTS\n  ek9 -t myApp.ek9\nRuns every @Test program in the project.\n\nRUN A SPECIFIC GROUP\n  ek9 -tg database myApp.ek9\nRuns only tests marked @Test: \"database\". All other tests are skipped.\n\nLIST TESTS WITHOUT RUNNING\n  ek9 -tL myApp.ek9\nDiscovery mode. Lists all @Test programs with their group names but does not execute them. Useful for CI/CD pipelines that need to enumerate tests before running.\n\nCOMBINE FLAGS\nGroup filter + list:\n  ek9 -tL -tg network myApp.ek9\nGroup filter + format:\n  ek9 -t2 -tg api myApp.ek9\nGroup filter + profiling:\n  ek9 -tp -tg perf myApp.ek9\n\nOUTPUT FORMATS\nAll output format flags work with -tg:\n  -t0  terse    -t1  human (default)\n  -t2  JSON     -t3  JUnit XML\n  -t4  coverage -t5  verbose coverage\n  -t6  HTML     Append p for profiling\n\nCI/CD WORKFLOW\nA typical CI pipeline might:\n  ek9 -tL myApp.ek9           # list tests\n  ek9 -t2 myApp.ek9           # run all, JSON output\n  ek9 -t3 myApp.ek9           # JUnit XML for CI dashboards\n  ek9 -t6 myApp.ek9           # HTML report as artifact\n\nSee Q806 for test grouping. See Q207 for output formats. See Q206 for coverage.","ek9Example":"defines module qa.testdeep.running.specific\n\n  defines function\n\n    fetchUser() as pure\n      -> userId as Integer\n      <- rtn as String: `user-${userId}`\n\n  defines program\n\n    // === DIFFERENT TEST GROUPS ===\n\n    @Test: \"api\"\n    FetchUserTest()\n      user <- fetchUser(42)\n      assert user == \"user-42\"\n\n    @Test: \"api\"\n    FetchAnotherUserTest()\n      user <- fetchUser(1)\n      assert user == \"user-1\"\n\n    @Test: \"validation\"\n    ValidateInputTest()\n      name <- \"EK9\"\n      assert name?\n      assert name.length() > 0\n\n    // Run only api tests:     ek9 -tg api thisFile.ek9\n    // Run only validation:    ek9 -tg validation thisFile.ek9\n    // List all tests:         ek9 -tL thisFile.ek9\n    // JSON output for CI:     ek9 -t2 thisFile.ek9\n    // HTML report:            ek9 -t6 thisFile.ek9","migrationContext":"Java: JUnit -t tag filtering, Maven -Dtest=ClassName, Gradle --tests filter. Python: pytest -k expression, pytest -m marker. Rust: cargo test test_name, --test-threads=1. Go: go test -run TestRegex. EK9: -tg groupname for group filtering, -tL for test discovery.","keywords":["ci","filter","format","group","list","pipeline","run","specific","tL","tg"],"primaryTopics":["run specific tests","test filtering","test list"],"typicalErrors":[],"companions":[]}
{"id":808,"category":"Testing","question":"How do I mock dependencies in EK9 tests without Mockito?","url":"https://ek9.io/qa/QA0808.html","alternatePhrasings":["What is the EK9 equivalent of Mockito?","How do I use traits for test doubles in EK9?","How do I test components with injected dependencies in EK9?"],"answer":"EK9 does not need Mockito, unittest.mock, or any mocking library. Instead, you use traits and dependency injection to create test doubles at the language level.\n\nTHE PATTERN\n1. Define behaviour as a trait (interface)\n2. Production code depends on the trait, not the concrete implementation\n3. In tests, provide a test implementation of the trait\n4. DI wiring in the application block selects which implementation to use\n\nWHY NO MOCKING FRAMEWORK\nMocking frameworks exist because languages allow tight coupling to concrete classes. EK9 prevents this:\n- Components depend on traits (interfaces), not concrete types\n- The DI system wires implementations at compile time\n- Test implementations are just another implementation of the trait\n- No reflection, no bytecode manipulation, no proxy objects\n\nTRAIT-BASED TEST DOUBLES\nDefine a trait:\n  DataStore as trait\n    fetch() as pure abstract\n      -> key as String\n      <- rtn as String?\nProduction implementation:\n  RealDataStore is DataStore\n    override fetch() as pure\n      -> key as String\n      <- rtn as String: lookupDatabase(key)\nTest implementation:\n  FakeDataStore is DataStore\n    override fetch() as pure\n      -> key as String\n      <- rtn as String: \"test-value\"\n\nTEST APPLICATION\nWire the test double in a test application block:\n  defines application\n    TestApp\n      register FakeDataStore() as DataStore\n\nADVANTAGES\n- Compile-time verified: missing method implementations caught immediately\n- No magic: test doubles are normal classes implementing traits\n- Reusable: same test double works across all tests\n- Type-safe: cannot accidentally mock the wrong method signature\n\nSee Q227 for DI basics. See Q324 for DI deep dive. See Q155 for basic testing.","ek9Example":"defines module qa.testdeep.mocking\n\n  defines trait\n\n    <?-\n      The trait defines the contract.\n      Production and test code both implement this.\n    -?>\n    Formatter\n      format() as pure abstract\n        -> number as Integer\n        <- rtn as String?\n\n  defines class\n\n    // === PRODUCTION IMPLEMENTATION ===\n\n    FancyFormatter with trait of Formatter\n      override format() as pure\n        -> number as Integer\n        <- rtn as String: `[${number}]`\n\n    // === TEST DOUBLE ===\n\n    SimpleFormatter with trait of Formatter\n      override format() as pure\n        -> number as Integer\n        <- rtn as String: $number\n\n  defines program\n\n    MockingDemo()\n      stdout <- Stdout()\n\n      // Production: uses FancyFormatter\n      fancy <- FancyFormatter()\n      stdout.println(fancy.format(42))\n\n      // Test: uses SimpleFormatter (the \"mock\")\n      simple <- SimpleFormatter()\n      stdout.println(simple.format(42))\n\n      // Both implement Formatter trait\n      // DI wiring chooses which one:\n      //   Production app: register FancyFormatter() as Formatter\n      //   Test app:       register SimpleFormatter() as Formatter\n\n      stdout.println(\"No Mockito needed\")\n      stdout.println(\"Traits replace mocking frameworks\")","migrationContext":"Java: Mockito mock()/when()/verify(), Spring @MockBean. Python: unittest.mock.patch(), MagicMock. Rust: mockall crate with #[automock]. Go: interfaces + hand-written fakes (standard pattern). Kotlin: MockK or Mockito-kotlin. EK9: trait implementations as test doubles, DI wiring selects implementation, no mocking library needed.","keywords":["dependency","double","fake","injection","interface","mock","mockito","stub","test","trait"],"primaryTopics":["mocking","test doubles","mockito equivalent"],"typicalErrors":[{"error":"E50020","correct":"FancyFormatter with trait of Formatter","incorrect":"FancyFormatter extends Formatter","explanation":"A class implements a trait with 'with trait of', not 'extends' — using 'extends' on a trait is an incompatible genus (class vs trait). See ek9 -h E50020 for details."}],"companions":[]}
{"id":809,"category":"Testing","question":"How does EK9 enforce test quality at compile time?","url":"https://ek9.io/qa/QA0809.html","alternatePhrasings":["What is an empty test error in EK9?","What is an orphan assertion in EK9?","How does EK9 prevent meaningless tests?"],"answer":"EK9 validates test quality during compilation. Tests that verify nothing or have unreachable assertions are compiler errors, not warnings.\n\nE81007: EMPTY TEST\nA @Test program must validate something. If it has no assert statements, no assertThrows, and no expected_output.txt companion file, the compiler rejects it:\n  @Test\n  EmptyTest()\n    result <- calculate(5)\n    stdout <- Stdout()\n    stdout.println($result)   // prints but verifies nothing\nThis fails with E81007. Fix by adding assert result == 25 or providing expected_output.txt.\n\nE81011: ORPHAN ASSERTION\nAssertions must be reachable from at least one @Test program through the call graph:\n  helperFunction()\n    assert someCondition()    // orphan — no @Test calls this\nIf no @Test program calls helperFunction() directly or indirectly, the assert is unreachable and the compiler reports E81011. This catches forgotten test wiring.\n\nE81012: PRODUCTION ASSERTION\nAssert and assertThrows are only valid in @Test programs or functions called from @Test programs. Using them in production code is a compiler error.\n\nQUALITY METRICS APPLY TO TESTS\nTest code is held to the same quality standards as production code:\n- Complexity limits (cyclomatic, cognitive, nesting)\n- Cohesion and coupling checks\n- Variable naming rules (no temp, flag, data, value)\n- Reference ordering (alphabetical)\n- Unused parameter detection\n\nWHY THIS MATTERS\nIn Java/Python, a test that calls code but asserts nothing passes silently — false coverage that hides bugs. EK9 makes this a compile error. You cannot have a test that does not test.\n\nSee Q804 for why built-in testing. See Q155 for writing tests. See Q310 for code quality metrics.","ek9Example":"defines module qa.testdeep.quality.enforcement\n\n  defines function\n\n    compute() as pure\n      -> n as Integer\n      <- rtn as Integer: n * n\n\n  defines program\n\n    // === VALID TESTS: ASSERT SOMETHING ===\n\n    @Test\n    ComputeSquareTest()\n      result <- compute(7)\n      assert result == 49\n\n    @Test\n    ComputeZeroTest()\n      result <- compute(0)\n      assert result == 0\n\n    TestQualityDemo()\n      stdout <- Stdout()\n      stdout.println(\"E81007: empty test (no assertions)\")\n      stdout.println(\"E81011: orphan assertion (unreachable)\")\n      stdout.println(\"E81012: assert in production code\")\n      stdout.println(\"All quality metrics apply to test code\")","migrationContext":"Java: JUnit has no compile-time quality checks — empty tests pass silently. SonarQube can detect some patterns but is optional. Python: pytest allows empty tests, flake8 cannot detect missing assertions. Rust: empty #[test] functions compile and pass. Go: empty Test functions compile and pass. EK9: compile-time rejection of empty tests (E81007), orphan assertions (E81011), and production assertions (E81012).","keywords":["E81007","E81011","E81012","assertion","compile","empty","enforce","orphan","quality","validate"],"primaryTopics":["test quality","empty test","orphan assertion"],"typicalErrors":[{"error":"E81007","correct":"    @Test\n    ComputeSquareTest()\n      result <- compute(7)\n      assert result == 49","incorrect":"@Test\n    NoVerification()\n      result <- compute(10)\n      stdout <- Stdout()\n      stdout.println($result)","explanation":"A @Test program with no assert, no assertThrows, and no expected_output.txt is an empty test — it executes code but validates nothing. Add assertions or provide an expected output file. See ek9 -h E81007 for details."},{"error":"E81011","correct":"    @Test\n    ComputeSquareTest()\n      result <- compute(7)\n      assert result == 49\n\n    @Test\n    ComputeZeroTest()","incorrect":"verifyResult()\n      -> value as Integer\n      assert value > 0\n    // No @Test program calls verifyResult","explanation":"An assert statement must be reachable from at least one @Test program through the call graph. An unreachable assertion is dead test code. See ek9 -h E81011 for details."}],"companions":[]}
{"id":810,"category":"Testing","question":"How do I test output containing dynamic values like dates or GUIDs in EK9?","url":"https://ek9.io/qa/QA0810.html","alternatePhrasings":["What are output placeholders in EK9 tests?","How do I handle non-deterministic output in EK9 black-box tests?","How do I use {{Date}} and {{GUID}} in expected output files?"],"answer":"EK9 expected output files support typed placeholders for values that change between test runs. This lets you black-box test programs that print timestamps, GUIDs, or other dynamic data.\n\nTHE PROBLEM\nA program that prints the current date produces different output each run:\n  stdout.println(\"Started at: \" + $DateTime())\nThe expected_output.txt cannot contain a fixed date string.\n\nTHE SOLUTION: PLACEHOLDERS\nUse typed placeholders in expected output files:\n  Started at: {{DateTime}}\nThe test runner matches any valid DateTime value at that position.\n\nAVAILABLE PLACEHOLDERS\n  {{String}}         any string value\n  {{Integer}}        any integer\n  {{Float}}          any float\n  {{Boolean}}        true or false\n  {{Date}}           any valid date\n  {{Time}}           any valid time\n  {{DateTime}}       any valid date-time\n  {{Duration}}       any duration\n  {{Millisecond}}    any millisecond value\n  {{Money}}          any money value\n  {{Colour}}         any colour value\n  {{Dimension}}      any dimension value\n  {{GUID}}           any GUID/UUID\n  {{FileSystemPath}} any file path\n\nEXAMPLE EXPECTED OUTPUT FILE\n  Report generated: {{DateTime}}\n  Request ID: {{GUID}}\n  Items processed: {{Integer}}\n  Success: {{Boolean}}\n\nMIXING FIXED AND DYNAMIC\nPlaceholders can appear anywhere in a line:\n  User alice created at {{DateTime}} with ID {{GUID}}\nFixed text is matched exactly. Placeholders match their typed pattern.\n\nSee Q204 for black-box testing. See Q205 for parameterized tests. See Q208 for more placeholder details.","ek9Example":"defines module qa.testdeep.output.placeholders\n\n  defines program\n\n    // === PROGRAM WITH DYNAMIC OUTPUT ===\n    // The expected_output.txt companion would use placeholders\n    // to match non-deterministic values.\n\n    PlaceholderDemo()\n      stdout <- Stdout()\n\n      // These values change every run\n      now <- DateTime()\n      requestId <- GUID()\n      itemCount <- 42\n\n      stdout.println(\"Report generated: \" + $now)\n      stdout.println(\"Request ID: \" + $requestId)\n      stdout.println(\"Items processed: \" + $itemCount)\n      stdout.println(\"Success: true\")","migrationContext":"Java: JUnit requires custom matchers or regex assertions for dynamic values. Python: pytest approx() for floats, custom assertion helpers for dates. Rust: no built-in equivalent, custom comparison functions. Go: testify suite with custom matchers. EK9: built-in typed placeholders in expected_output.txt files — {{DateTime}}, {{GUID}}, {{Integer}} etc.","keywords":["black-box","date","dynamic","expected","guid","non-deterministic","output","placeholder","template","timestamp"],"primaryTopics":["output placeholder","dynamic test output","test placeholder"],"typicalErrors":[{"error":"E50060","correct":"      stdout.println(\"Request ID: \" + $requestId)","incorrect":"      stdout.println(\"Request ID: \" + requestId.toString())","explanation":"GUID has no toString() method — use the $ prefix operator for String conversion. See ek9 -h E50060 for details."}],"companions":[]}
{"id":811,"category":"Testing","question":"How do coverage thresholds and exit codes work in EK9?","url":"https://ek9.io/qa/QA0811.html","alternatePhrasings":["What happens if my EK9 test coverage is below 80%?","What do EK9 test exit codes mean?","How does EK9 enforce minimum code coverage?"],"answer":"EK9 enforces an 80% coverage threshold as a quality gate. Test exit codes distinguish between test failures and coverage failures.\n\nEXIT CODES\nThe test runner returns specific exit codes:\n  0   All tests passed AND coverage >= 80%\n  11  One or more tests failed\n  12  All tests passed BUT coverage < 80%\n\nThis means a CI/CD pipeline can distinguish between:\n- Everything good (0)\n- Broken code (11) — fix the failing tests first\n- Insufficient testing (12) — add more tests\n\nTHE 80% THRESHOLD\nEK9 enforces 80% line coverage by default. This is not configurable per-project — it is a language-level quality gate. If your tests pass but only cover 60% of the code, the build fails with exit code 12.\n\nWHY 80%\nIndustry research shows 80% is the sweet spot:\n- Below 80%: significant untested paths likely contain bugs\n- Above 80%: diminishing returns, risk of gaming metrics\n- 100%: usually not worth the effort for the last 20%\n\nCOVERAGE MODES\nThree instrumentation modes:\n  SET     records whether each line was executed (default, lowest overhead)\n  COUNT   counts how many times each line ran (identifies hot paths)\n  ATOMIC  thread-safe counting for concurrent tests\n\nVIEWING COVERAGE\n  ek9 -t4 myApp.ek9    JSON output with coverage data\n  ek9 -t5 myApp.ek9    verbose coverage breakdown per function\n  ek9 -t6 myApp.ek9    interactive HTML dashboard with source highlighting\n\nThe HTML dashboard (-t6) shows coverage per function, complexity badges, and readability scores.\n\nCI/CD INTEGRATION\n  ek9 -t2 myApp.ek9    JSON for programmatic parsing\n  ek9 -t3 myApp.ek9    JUnit XML for CI dashboards (Jenkins, GitLab)\n  echo $?              check exit code: 0=pass, 11=fail, 12=low coverage\n\nSee Q206 for coverage basics. See Q207 for output formats. See Q804 for why built-in testing.","ek9Example":"defines module qa.testdeep.coverage.thresholds\n\n  defines function\n\n    classify() as pure\n      -> n as Integer\n      <- rtn as String?\n\n      if n > 0\n        rtn: \"positive\"\n      else if n < 0\n        rtn: \"negative\"\n      else\n        rtn: \"zero\"\n\n  defines program\n\n    // === COVERAGE THRESHOLD DEMO ===\n    // Tests must cover >= 80% of code to pass.\n    // Exit code 0  = pass + coverage OK\n    // Exit code 11 = test failure\n    // Exit code 12 = tests pass but coverage < 80%\n\n    @Test\n    ClassifyPositiveTest()\n      result <- classify(5)\n      assert result == \"positive\"\n\n    @Test\n    ClassifyNegativeTest()\n      result <- classify(-3)\n      assert result == \"negative\"\n\n    @Test\n    ClassifyZeroTest()\n      result <- classify(0)\n      assert result == \"zero\"\n\n    // All three branches covered = good coverage.\n    // Missing any branch risks exit code 12.","migrationContext":"Java: JaCoCo with Maven enforcer plugin, configurable thresholds per package. Python: coverage.py with --fail-under=80. Rust: cargo-tarpaulin with --fail-under 80. Go: go test -cover shows percentage but no threshold enforcement built in. EK9: 80% threshold enforced by the compiler, exit code 12 for insufficient coverage, no configuration needed.","keywords":["ci","code","coverage","eighty","enforce","exit","gate","percent","pipeline","threshold"],"primaryTopics":["coverage threshold","test exit codes","80 percent coverage"],"typicalErrors":[],"companions":[]}
{"id":812,"category":"Streams and Pipelines","question":"What are common stream pipeline errors in EK9?","url":"https://ek9.io/qa/QA0812.html","alternatePhrasings":["Why does my stream pipeline fail to compile in EK9?","How do I fix stream head/tail/skip errors in EK9?","What are the rules for stream limiting operators in EK9?"],"answer":"EK9 stream pipelines validate function signatures and types at compile time. Common errors involve head/tail/skip arguments, filter predicates, and type mismatches.\n\nHEAD, TAIL, SKIP ARGUMENTS\nThese limiting operators accept:\n- No argument (defaults to 1): cat items | head > result\n- Integer literal (must be > 0): cat items | head 5 > result\n- Integer variable: cat items | head with limit > result\n- Supplier function (no params, returns Integer): cat items | head JustOne > result\n\nUsing zero, negative, non-integer, or functions with parameters triggers compile errors.\n\nFILTER AND SPLIT\nFilter requires a predicate (returns Boolean):\n  cat items | filter by isPositive > result\nThe function must take one parameter matching the stream type and return Boolean.\n\nCALL AND ASYNC\nStream call/async execute function delegates flowing through the pipeline:\n  cat [getSteve, getLimb] | call > collector\nFunctions must be zero-argument suppliers returning a value. Non-function types, void functions, and functions requiring parameters are all compile errors.\n\nSee Q126 for complete stream reference. See Q204 for black-box tests.","ek9Example":"defines module qa.streams.pipeline.errors\n\n  defines function\n\n    JustOne()\n      <- rtn <- 1\n\n    JustDate()\n      <- rtn <- 2024-10-01\n\n    AcceptsArgument()\n      -> arg0 as Integer\n      <- rtn as Integer: arg0\n\n    isShort() as pure\n      -> item as String\n      <- rtn as Boolean: item.length() < SHORT_THRESHOLD\n\n  defines constant\n    SHORT_THRESHOLD <- 5\n\n  defines program\n\n    StreamHeadDemo()\n      stdout <- Stdout()\n\n      // === head with no argument (defaults to 1) ===\n      cat [\"alpha\", \"beta\", \"gamma\"]\n        | head\n        > stdout\n\n      // === head with integer literal ===\n      cat [\"alpha\", \"beta\", \"gamma\"]\n        | head 2\n        > stdout\n\n      // === head with supplier function (reference) ===\n      cat [\"alpha\", \"beta\", \"gamma\"]\n        | head JustOne\n        > stdout\n\n      // === head with supplier function (call) ===\n      cat [\"alpha\", \"beta\", \"gamma\"]\n        | head JustOne()\n        > stdout\n\n      // === filter with predicate ===\n      cat [\"hi\", \"hello\", \"hey\", \"greetings\"]\n        | filter by isShort\n        > stdout","migrationContext":"Java: Stream.limit(n) and skip(n) accept long — zero and negative accepted at compile time, fail at runtime. Python: itertools.islice accepts any integer silently. Rust: .take(n) and .skip(n) accept usize with no value validation. EK9: compile-time validation of positive integers, correct function signatures, and type compatibility throughout the pipeline.","keywords":["async","call","error","filter","head","limit","pipeline","skip","stream","tail"],"primaryTopics":["stream errors","stream head tail skip","stream pipeline mistakes"],"typicalErrors":[{"error":"E07450","correct":"| head JustOne","incorrect":"| head AcceptsArgument","explanation":"Stream head/tail/skip supplier functions must take no parameters. AcceptsArgument requires an Integer argument so cannot be used as a count supplier. See ek9 -h E07450 for details."},{"error":"E07870","correct":"| head JustOne()","incorrect":"| head JustDate()","explanation":"Stream head/tail/skip function call must return Integer. JustDate returns a Date which cannot provide a count. See ek9 -h E07870 for details."}],"companions":[]}
{"id":813,"category":"Streams and Pipelines","question":"How do call and async work in EK9 stream pipelines?","url":"https://ek9.io/qa/QA0813.html","alternatePhrasings":["How do I execute function delegates in an EK9 stream?","What is the difference between call and async in EK9 streams?","Why does my stream call fail with TYPE_MUST_BE_FUNCTION?"],"answer":"EK9 stream pipelines support call (sequential) and async (concurrent) operators to execute function delegates flowing through the pipeline.\n\nCALL AND ASYNC\nThe call and async operators execute functions that are items in the stream:\n  cat [getSteve, getLimb] | call > collector\nThe stream must contain functions. call executes each sequentially; async executes them concurrently on separate threads.\n\nFUNCTION REQUIREMENTS\nFunctions used with call/async must:\n1. Be actual function delegates (not integers, strings, or class instances)\n2. Take no parameters (zero-argument suppliers)\n3. Return a value (the return becomes the next stream element)\n\nCOMMON MISTAKES\n- Streaming non-function values: cat [1, 2] | call triggers E04040 TYPE_MUST_BE_FUNCTION\n- Using void functions: cat [noReturn] | call triggers E07490 FUNCTION_MUST_RETURN_VALUE\n- Using functions with parameters: cat [needsArg] | call triggers E06310 REQUIRE_NO_ARGUMENTS\n- Passing a function TO call: cat items | call myFunc triggers E07880 FUNCTION_OR_DELEGATE_NOT_REQUIRED\n\nDYNAMIC FUNCTIONS\nIf a function needs captured data, use a dynamic function:\n  supplier <- () is AbstractFn as function (rtn: capturedValue)\n  cat [supplier] | call > result\n\nSee Q812 for stream head/tail/skip errors. See Q126 for complete stream reference.","ek9Example":"defines module qa.streams.call.async.rules\n\n  defines function\n\n    getGreeting()\n      <- rtn <- \"Hello\"\n\n    getWorld()\n      <- rtn <- \"World\"\n\n  defines program\n\n    StreamFunctionDemo()\n      stdout <- Stdout()\n\n      // These zero-arg functions return String values.\n      // They are suitable for use with call/async in streams:\n      //   cat [getGreeting, getWorld] | call > stdout\n\n      greeting <- getGreeting()\n      stdout.println(greeting)\n\n      world <- getWorld()\n      stdout.println(world)","migrationContext":"Java: No built-in stream call/async. Use map(Supplier::get) or CompletableFuture.supplyAsync(). Python: map(func, iterable) for sequential, asyncio.gather() for parallel. Rust: .map(|f| f()) in iterators. Go: goroutines with channels. EK9: call and async are native pipeline operators with compile-time function signature validation.","keywords":["async","call","concurrent","delegate","execute","function","parallel","pipeline","stream","supplier"],"primaryTopics":["stream call async","function execution in stream"],"typicalErrors":[],"companions":[]}
{"id":814,"category":"Code Quality","question":"How does EK9 enforce nesting depth limits?","url":"https://ek9.io/qa/QA0814.html","alternatePhrasings":["What is the maximum nesting depth in EK9?","Why does my code fail with EXCESSIVE_NESTING?","How do I fix E11011 excessive nesting in EK9?"],"answer":"EK9 enforces a maximum nesting depth of 6 levels. Code nested deeper than 6 if/while/for/switch blocks triggers E11011 EXCESSIVE_NESTING at compile time.\n\nTHRESHOLD\nThe nesting depth threshold is 6 levels. Each control flow construct (if, while, for, switch, try) adds one level of nesting. At 7 levels, the compiler rejects the code.\n\nWHY THIS MATTERS\nDeeply nested code is hard to follow, hard to test, and correlates with bugs. Microsoft Research found that functions with nesting depth > 5 have 3x the defect rate of shallow functions.\n\nHOW TO FIX\n- Extract inner logic to separate functions\n- Use guard clauses to exit early\n- Use switch instead of nested if/else chains\n- Consider polymorphism for type-based branching\n\nNESTING VS COGNITIVE COMPLEXITY\nNesting depth (E11011, threshold 6) measures structural depth. Cognitive complexity (E11021, threshold 35) measures mental effort. Deep-but-simple code triggers nesting first. Shallow-but-complex code triggers cognitive first.\n\nSee Q310 for code quality overview. See Q312 for complexity metrics.","ek9Example":"defines module qa.quality.nesting.depth\n\n  defines function\n\n    // === AT THE BOUNDARY: 6 levels (maximum allowed) ===\n    classifyDepth() as pure\n      -> inputValue as Integer\n      <- result as Integer: 0\n\n      levelTwo <- 2\n      levelThree <- 3\n      levelFour <- 4\n      levelFive <- 5\n\n      if inputValue > 0\n        if inputValue > 1\n          if inputValue > levelTwo\n            if inputValue > levelThree\n              if inputValue > levelFour\n                if inputValue > levelFive\n                  result: 6\n\n  defines program\n\n    NestingDepthDemo()\n      stdout <- Stdout()\n\n      result <- classifyDepth(7)\n      stdout.println(\"Depth classification: \" + $result)\n\n      shallow <- classifyDepth(1)\n      stdout.println(\"Shallow: \" + $shallow)","migrationContext":"Java: SonarQube flags deep nesting as a code smell but it compiles fine. Rust: clippy warns but is suppressible. Go: cultural convention only. C#: Roslyn analyzers warn but don't block. EK9: nesting depth > 6 is a hard compiler error that cannot be bypassed.","keywords":["E11011","complexity","depth","extract","guard","nesting","quality","refactor","threshold"],"primaryTopics":["nesting depth","excessive nesting","E11011"],"typicalErrors":[{"error":"E11011","correct":"                if inputValue > levelFive\n                  result: 6","incorrect":"                if inputValue > levelFive\n                  if inputValue > 6\n                    result: 7","explanation":"Adding a 7th nesting level exceeds the maximum depth of 6. Extract the inner logic to a separate function or use guard clauses. See ek9 -h E11011 for details."}],"companions":[]}
{"id":815,"category":"Code Quality","question":"Why is calling a computational operator without using its result an error in EK9?","url":"https://ek9.io/qa/QA0815.html","alternatePhrasings":["What is E11050 DISCARDED_OPERATOR_RETURN?","Why can't I call an operator as a bare statement in EK9?","How do I fix discarded return value errors in EK9?"],"answer":"EK9 detects when a computational operator is called explicitly but its return value is discarded. This is dead code — the computation achieves nothing.\n\nWHAT TRIGGERS E11050\nCalling an operator via method syntax and ignoring the result:\n  a._eq(b)     // computes equality but throws away the Boolean\n  a.+(b)       // computes addition but throws away the result\nThese statements achieve nothing — the result is computed and discarded.\n\nMUTATING VS COMPUTATIONAL\nMutating operators return Void and are valid as statements:\n  a += b       // modifies a in place, no return to discard\n  counter++    // mutates counter, Void return\nComputational operators return values that must be used:\n  isEqual <- a == b    // use the result\n  sum <- a + b         // capture the value\n\nHOW TO FIX\n- Assign the result: isEqual <- a._eq(b)\n- Use in an expression: if a == b\n- If you want mutation, use the mutating operator: a += b\n\nSee Q310 for code quality overview. See Q316 for operator semantics.","ek9Example":"defines module qa.quality.discarded.return\n\n  defines program\n\n    DiscardedReturnDemo()\n      -> greeting as String\n      stdout <- Stdout()\n\n      farewell <- \"goodbye\"\n\n      // === CORRECT: capture the explicit operator call result ===\n      isEqual <- greeting.==(farewell)\n      stdout.println(`Equal: ${isEqual}`)\n\n      // === CORRECT: use in an expression ===\n      if greeting == farewell\n        stdout.println(\"same\")\n      else\n        stdout.println(\"different\")\n\n      // === CORRECT: explicit operator call with captured result ===\n      price <- 100\n      tax <- 15\n      sum <- price.+(tax)\n      require sum?\n\n      // === CORRECT: mutating operators are fine as statements ===\n      counter <- 0\n      counter++\n      stdout.println(\"Counter: \" + $counter)","migrationContext":"Java: silently discards operator results in expression statements. Python: same — no warning for unused computation. Rust: warns about unused Result but not operators. Go: requires using all return values. EK9: makes discarded computational operator returns a compile error.","keywords":["E11050","code","computational","dead","discarded","mutation","operator","quality","return"],"primaryTopics":["discarded return","E11050","operator return value"],"typicalErrors":[{"error":"E11050","correct":"      sum <- price.+(tax)\n      require sum?","incorrect":"      price.+(tax)\n      require price?","explanation":"Calling the + operator via explicit method syntax and discarding the result means the addition achieves nothing. Assign the result or use the mutating operator +=. See ek9 -h E11050 for details."}],"companions":[]}
{"id":816,"category":"Code Quality","question":"Why does EK9 require named arguments for multiple Boolean parameters?","url":"https://ek9.io/qa/QA0816.html","alternatePhrasings":["What is E11061 BOOLEAN_ARGUMENTS_REQUIRE_NAMES?","Why can't I pass two Boolean literals positionally in EK9?","How do I fix Boolean argument naming errors in EK9?"],"answer":"EK9 requires named arguments when a call passes 2 or more Boolean literals. This prevents the 'Boolean blindness' anti-pattern.\n\nTHE PROBLEM\nPositional Boolean arguments are unreadable at the call site:\n  connect('db', 5432, true, false)   // what do true and false mean?\nWithout checking the function signature, the reader has no idea what each Boolean controls.\n\nTHE RULE\nOne Boolean literal is fine — context usually makes it clear:\n  setVisible(true)                   // obviously sets visibility\nTwo or more Boolean literals trigger E11061:\n  connect('db', 5432, true, false)   // ERROR: ambiguous\n\nTHE FIX\nUse named arguments (EK9 requires all-or-nothing naming):\n  connect(host: 'db', port: 5432, useSsl: true, autoReconnect: false)\n\nSee Q310 for code quality overview. See Q694 for named arguments.","ek9Example":"defines module qa.quality.boolean.args\n\n  defines function\n\n    configureNetwork()\n      ->\n        host as String\n        useSsl as Boolean\n        autoReconnect as Boolean\n      stdout <- Stdout()\n      stdout.println(`Host: ${host} SSL: ${useSsl} Reconnect: ${autoReconnect}`)\n\n  defines program\n\n    BooleanArgsDemo()\n      stdout <- Stdout()\n\n      // === CORRECT: named arguments for 2+ Booleans ===\n      configureNetwork(host: \"db.example.com\", useSsl: true, autoReconnect: false)\n\n      // === CORRECT: single Boolean is fine positionally ===\n      stdout.println(\"Single Boolean: \" + $true)","migrationContext":"Java: no enforcement — Boolean parameters are a known code smell. Python: no enforcement but PEP 8 recommends keyword args. Swift: requires argument labels by default. Kotlin: supports named args but doesn't require them. EK9: 2+ Boolean literals = compile error unless named.","keywords":["E11061","ambiguous","arguments","boolean","named","parameters","quality","readability"],"primaryTopics":["boolean arguments","named arguments","E11061"],"typicalErrors":[{"error":"E11061","correct":"      configureNetwork(host: \"db.example.com\", useSsl: true, autoReconnect: false)","incorrect":"      configureNetwork(\"db.example.com\", true, false)","explanation":"Two Boolean literals (true, false) as positional arguments are ambiguous. Use named arguments so the reader knows what each Boolean controls. See ek9 -h E11061 for details."}],"companions":[]}
{"id":817,"category":"Code Quality","question":"Why does EK9 require named arguments for 4+ parameters?","url":"https://ek9.io/qa/QA0817.html","alternatePhrasings":["What is E11062 MANY_ARGUMENTS_REQUIRE_NAMES?","Why can't I pass 4 positional arguments in EK9?","How do I fix many arguments need names in EK9?"],"answer":"EK9 requires named arguments when a call passes 4 or more positional arguments. Beyond 3 arguments, the 3rd and 4th are routinely confused.\n\nTHE RULE\n3 or fewer positional arguments are fine:\n  connect(host, port, timeout)       // OK: 3 args\n4 or more trigger E11062:\n  connect(host, port, timeout, retries)  // ERROR: use named\n\nTHE FIX\nUse named arguments (EK9 requires all-or-nothing naming):\n  connect(host: host, port: port, timeout: timeout, retries: retries)\n\nWHY THIS MATTERS\nMiller's Law: humans can track 7 +/- 2 items. At 4+ parameters, positional confusion causes bugs that are invisible at the call site. Named arguments make every parameter's purpose visible.\n\nSee Q694 for named argument patterns. See Q816 for Boolean argument naming.","ek9Example":"defines module qa.quality.many.args\n\n  defines function\n\n    scheduleJob()\n      ->\n        jobName as String\n        path as String\n        retries as Integer\n        interval as Integer\n      stdout <- Stdout()\n      stdout.println(`Scheduled ${jobName} at ${path} retries=${retries} interval=${interval}`)\n\n  defines program\n\n    ManyArgsDemo()\n      stdout <- Stdout()\n\n      // === CORRECT: named arguments for 4+ parameters ===\n      scheduleJob(jobName: \"backup\", path: \"/data\", retries: 3, interval: 60)\n\n      // === CORRECT: 3 or fewer positional is fine ===\n      stdout.println(\"Three args or fewer: no naming required\")","migrationContext":"Java: no enforcement. Python: PEP 8 recommends keyword args for clarity. Swift: requires argument labels by default. Kotlin: supports named args. EK9: 4+ positional args = compile error.","keywords":["E11062","arguments","many","named","parameters","positional","quality","readability"],"primaryTopics":["many arguments","named arguments","E11062"],"typicalErrors":[{"error":"E11062","correct":"      scheduleJob(jobName: \"backup\", path: \"/data\", retries: 3, interval: 60)","incorrect":"      scheduleJob(\"backup\", \"/data\", 3, 60)","explanation":"4 positional arguments exceed the readability threshold. Use named arguments so each parameter's purpose is clear at the call site. See ek9 -h E11062 for details."}],"companions":[]}
{"id":818,"category":"Code Quality","question":"Why does EK9 reject self-cancelling arithmetic?","url":"https://ek9.io/qa/QA0818.html","alternatePhrasings":["What is E08083 CONSTANT_ARITHMETIC?","Why can't I write x - x or x / x in EK9?","How does EK9 detect tautological arithmetic?"],"answer":"EK9 detects self-cancelling arithmetic expressions at compile time. Operations like x - x, x / x, x mod 1, and x rem 1 always produce a constant result regardless of x, making them dead code or logic errors.\n\nDETECTED PATTERNS\n  amount - amount      // always 0\n  amount / amount      // always 1 (or division by zero if 0)\n  amount mod amount    // always 0\n  amount rem amount    // always 0\n  amount mod 1         // always 0\n  amount rem 1         // always 0\n\nWHY THIS MATTERS\nThese patterns usually indicate copy-paste errors where the developer meant to use a different variable on one side. The compiler catches the mistake immediately.\n\nHOW TO FIX\n- Check if you meant a different variable on one side\n- If you genuinely want 0, assign it directly: result: 0\n- If you genuinely want 1, assign it directly: result: 1\n\nSee Q310 for code quality overview. See Q734 for other tautology detection.","ek9Example":"defines module qa.quality.constant.arithmetic\n\n  defines function\n\n    calculateRemaining() as pure\n      ->\n        amount as Integer\n        deduction as Integer\n      <- result as Integer?\n\n      result: amount - deduction\n\n  defines program\n\n    ConstantArithmeticDemo()\n      stdout <- Stdout()\n\n      remaining <- calculateRemaining(100, 25)\n      stdout.println(`Remaining: ${remaining}`)","migrationContext":"Java: no detection — self-cancelling arithmetic compiles silently. Python: no detection. Rust: clippy warns about some patterns. Go: no detection. EK9: compile-time error for all self-cancelling arithmetic.","keywords":["E08083","arithmetic","code","constant","dead","quality","self-cancelling","tautology"],"primaryTopics":["constant arithmetic","tautological expression","E08083"],"typicalErrors":[{"error":"E08083","correct":"      result: amount - deduction","incorrect":"      result: amount - amount","explanation":"Subtracting a variable from itself always produces 0. This is usually a copy-paste error where you meant a different variable. See ek9 -h E08083 for details."}],"companions":[]}
{"id":819,"category":"Code Quality","question":"Why does EK9 reject duplicate property fields in records?","url":"https://ek9.io/qa/QA0819.html","alternatePhrasings":["What is E02010 DUPLICATE_PROPERTY_FIELD?","Why can't I redeclare a parent field in a child record?","How does EK9 prevent property shadowing?"],"answer":"EK9 prevents child records from redeclaring properties that already exist in the parent. Property shadowing creates confusion about which field is being accessed.\n\nTHE RULE\nA child record cannot have a property with the same name as any inherited property:\n  BaseRecord\n    name as String\n  ChildRecord extends BaseRecord\n    name as String    // ERROR: E02010 duplicate property\n\nWHY THIS MATTERS\nProperty shadowing in Java causes subtle bugs: super.name and this.name refer to different fields. Code that accesses the field through the base type gets the wrong value. EK9 eliminates this by rejecting the duplicate at compile time.\n\nHOW TO FIX\n- Use a different name in the child: childName as String\n- If you need to override behaviour, use a method instead\n\nSee Q97 for class vs record guidance. See Q96 for inheritance.","ek9Example":"defines module qa.quality.duplicate.property\n\n  defines record\n\n    BaseRecord as open\n      label as String: String()\n      default operator ?\n\n    ChildRecord extends BaseRecord\n      childLabel as String: String()\n      default operator ?\n\n  defines program\n\n    DuplicatePropertyDemo()\n      stdout <- Stdout()\n\n      child <- ChildRecord()\n      stdout.println(`Label: ${child.label}`)\n      stdout.println(`Child label: ${child.childLabel}`)","migrationContext":"Java: allows field shadowing silently — a major source of bugs. Python: instance attributes shadow freely. Rust: no inheritance, so not applicable. Go: embedded structs can shadow. EK9: duplicate property field is a compile error.","keywords":["E02010","duplicate","field","inheritance","property","quality","record","shadow"],"primaryTopics":["duplicate property","field shadowing","E02010"],"typicalErrors":[{"error":"E02010","correct":"    ChildRecord extends BaseRecord\n      childLabel as String: String()","incorrect":"    ChildRecord extends BaseRecord\n      label as String: String()","explanation":"The parent BaseRecord already has a property named 'label'. Redeclaring it in the child creates a duplicate. Use a different name. See ek9 -h E02010 for details."}],"companions":[]}
{"id":820,"category":"Code Quality","question":"Why does EK9 reject redundant empty checks on collections?","url":"https://ek9.io/qa/QA0820.html","alternatePhrasings":["What is E08092 REDUNDANT_EMPTY_CHECK?","What is E08093 NEVER_EMPTY_CHECK?","Why does EK9 warn about checking if a new list is empty?"],"answer":"EK9 detects redundant empty checks using data flow analysis. A freshly created collection is always empty, so checking 'if items empty' immediately after creation is redundant — the condition is always true.\n\nE08092: REDUNDANT EMPTY CHECK\nChecking 'empty' on a provably empty collection:\n  items <- List() of String\n  if items empty           // ALWAYS true — just created\n    result: \"empty\"        // this always executes\n\nE08093: NEVER EMPTY CHECK\nChecking 'not empty' on a provably empty collection:\n  items <- List() of String\n  if items not empty       // ALWAYS false — just created\n    result: \"has items\"    // this never executes (dead code)\n\nWHEN CHECKS ARE VALID\nAfter items have been added or the collection comes from a parameter:\n  items <- List() of String\n  items += \"hello\"\n  if items empty           // valid — state changed since creation\n    result: \"empty\"\n\nSee Q310 for code quality overview. See Q734 for tautology detection.","ek9Example":"defines module qa.quality.empty.check\n\n  defines function\n\n    checkCollection()\n      -> greeting as String\n      <- result as String: String()\n\n      items <- List() of String\n      items += greeting\n      if items empty\n        result: \"empty\"\n      else\n        result: \"has items\"\n\n  defines program\n\n    EmptyCheckDemo()\n      stdout <- Stdout()\n\n      result <- checkCollection(\"hello\")\n      stdout.println(`Collection: ${result}`)","migrationContext":"Java: no detection — redundant isEmpty() compiles silently. Python: no detection. Rust: no detection. Go: no detection. EK9: compile-time error for provably redundant or impossible empty checks.","keywords":["E08092","E08093","code","collection","dead","dict","empty","list","redundant","tautology"],"primaryTopics":["redundant empty check","E08092","E08093"],"typicalErrors":[{"error":"E08092","correct":"      items += greeting","incorrect":"      items += greeting\n      items := List() of String","explanation":"Reassigning items to a freshly-created empty list immediately before 'if items empty' makes the check provably always true, so the compiler flags a redundant empty check. See ek9 -h E08092 for details."}],"companions":[]}
{"id":821,"category":"Code Quality","question":"Why can't I construct an abstract type directly in EK9?","url":"https://ek9.io/qa/QA0821.html","alternatePhrasings":["What is E10030 CONSTRUCTOR_USED_ON_ABSTRACT_TYPE?","Why does EK9 reject new MyAbstractClass()?","How do I fix abstract type construction errors in EK9?"],"answer":"EK9 prevents direct construction of abstract types at compile time. Abstract types define contracts — they must be extended with concrete implementations.\n\nTHE RULE\nYou cannot call the constructor of an abstract class, function, or generic parameterized with an abstract type:\n  Shape as abstract    // abstract — no direct construction\n  s <- Shape()         // ERROR: E10030\n\nHOW TO FIX\n- Create a concrete subclass and construct that instead\n- Use a dynamic class/function for inline implementation\n- If using generics, parameterize with a concrete type\n\nWHY THIS MATTERS\nAbstract types may have abstract methods with no implementation. Constructing them would create an object that cannot respond to all its methods. EK9 catches this at compile time rather than allowing it to fail at runtime.\n\nSee Q97 for class vs record. See Q96 for inheritance.","ek9Example":"defines module qa.quality.abstract.construction\n\n  defines class\n\n    Shape as abstract\n      default operator ?\n\n    Circle extends Shape\n      default Circle()\n      default operator ?\n\n  defines program\n\n    AbstractConstructionDemo()\n      stdout <- Stdout()\n\n      // === CORRECT: construct concrete subclass ===\n      shape <- Circle()\n      stdout.println(`Shape created: ${shape?}`)","migrationContext":"Java: cannot instantiate abstract class — runtime InstantiationError if attempted via reflection. Python: ABCMeta raises TypeError at runtime. Rust: traits cannot be constructed (no inheritance). Go: interfaces have no constructors. EK9: compile-time error for abstract construction.","keywords":["E10030","abstract","concrete","construction","constructor","implementation","type"],"primaryTopics":["abstract construction","E10030"],"typicalErrors":[{"error":"E50080","correct":"      shape <- Circle()","incorrect":"      shape <- Shape()","explanation":"Shape is abstract and cannot be constructed directly. Use a concrete subclass like Circle instead. See ek9 -h E50080 for details."}],"companions":[]}
{"id":822,"category":"Code Quality","question":"When can I use super in EK9?","url":"https://ek9.io/qa/QA0822.html","alternatePhrasings":["What is E05080 INAPPROPRIATE_USE_OF_SUPER?","Why does EK9 reject my use of super?","Where is super valid in EK9?"],"answer":"EK9 restricts the use of 'super' to specific contexts within subclass methods. Using super as a standalone expression, assigning it to a variable, or passing it as an argument is not allowed.\n\nVALID USES OF SUPER\nSuper is valid only for calling parent methods:\n  super.methodName()         // call parent implementation\n\nINVALID USES OF SUPER\n  theSuper <- super          // ERROR: can't assign super to variable\n  theSuper := super          // ERROR: can't reassign to super\n  this.callMethod(super)     // ERROR: can't pass super as argument\n\nWHY THIS RESTRICTION\nSuper is not an object reference — it is a dispatch mechanism. Allowing super as a value would create aliasing problems and break the inheritance contract. EK9 enforces that super is only used for method dispatch.\n\nSee Q96 for inheritance. See Q97 for class vs record.","ek9Example":"defines module qa.quality.super.misuse\n\n  defines class\n\n    BaseWidget as open\n      getName() as pure\n        <- rtn as String: \"BaseWidget\"\n      default operator ?\n\n    DerivedWidget extends BaseWidget\n      override getName() as pure\n        <- rtn as String: \"DerivedWidget\"\n\n      displayParentName()\n        stdout <- Stdout()\n        // === CORRECT: super for method dispatch ===\n        name <- super.getName()\n        stdout.println(`Parent: ${name}`)\n\n  defines program\n\n    SuperMisuseDemo()\n      widget <- DerivedWidget()\n      widget.displayParentName()","migrationContext":"Java: super can be used for method calls and constructor chaining but not as a standalone value. Python: super() returns a proxy object. Rust: no super — use trait default methods. Go: embedded struct accessed by name. EK9: super restricted to method dispatch only.","keywords":["E05080","class","dispatch","inheritance","method","parent","super"],"primaryTopics":["super keyword","E05080","inappropriate use of super"],"typicalErrors":[{"error":"E05080","correct":"        name <- super.getName()","incorrect":"        name <- super","explanation":"Super cannot be used as a standalone expression or converted to a string. It is only valid for dispatching method calls to the parent class. See ek9 -h E05080 for details."}],"companions":[]}
{"id":823,"category":"Code Quality","question":"Why must certain operators return specific types in EK9?","url":"https://ek9.io/qa/QA0823.html","alternatePhrasings":["What is E07410 MUST_RETURN_SAME_AS_CONSTRUCT_TYPE?","Why does my operator ~ return the wrong type?","What are operator return type rules in EK9?"],"answer":"EK9 enforces strict return type rules for operators. Some operators must return the same type as the construct they belong to.\n\nOPERATOR RETURN TYPE RULES\nThe ~ (negate/complement) operator must return the same type as the class:\n  MyNumber\n    operator ~ as pure\n      <- rtn as MyNumber   // CORRECT: returns same type\n\nReturning a different type triggers E07410:\n  MyNumber\n    operator ~ as pure\n      <- rtn as Float      // ERROR: must return MyNumber\n\nOTHER OPERATOR RULES\n- Comparison operators (==, <>, <, >) must return Boolean\n- The <=> (spaceship) operator must return Integer\n- The #? (hashcode) operator must return Integer\n- The $ (string) operator must return String\n- The ~ (negate) operator must return the same construct type\n\nSee Q316 for operator semantics. See Q310 for code quality.","ek9Example":"defines module qa.quality.operator.return\n\n  defines class\n\n    Temperature\n      degrees as Float: 0.0\n\n      Temperature() as pure\n        -> initialDegrees as Float\n        this.degrees :=: initialDegrees\n\n      operator ~ as pure\n        <- rtn as Temperature?\n        negated <- 0.0 - degrees\n        rtn: Temperature(negated)\n\n      operator $ as pure\n        <- rtn as String: $degrees\n\n      default operator ?\n\n  defines program\n\n    OperatorReturnDemo()\n      stdout <- Stdout()\n\n      warm <- Temperature(25.0)\n      cold <- ~warm\n      stdout.println(`Warm: ${warm}`)\n      stdout.println(`Negated: ${cold}`)","migrationContext":"Java: operator overloading not supported (except + for String). C++: operator overloading has no return type enforcement. Rust: trait-based operators enforce return types via associated types. Python: __neg__ can return any type. EK9: compile-time return type enforcement for all operators.","keywords":["E07410","complement","construct","negate","operator","return","type"],"primaryTopics":["operator return type","E07410"],"typicalErrors":[{"error":"E07410","correct":"      operator ~ as pure\n        <- rtn as Temperature?\n        negated <- 0.0 - degrees\n        rtn: Temperature(negated)","incorrect":"      operator ~ as pure\n        <- rtn as Float?\n        negated <- 0.0 - degrees\n        rtn: negated","explanation":"The ~ operator must return the same type as the class it belongs to; Temperature's ~ must return Temperature, not Float. See ek9 -h E07410 for details."}],"companions":[]}
{"id":824,"category":"Streams and Pipelines","question":"Why must stream functions have exactly one parameter in EK9?","url":"https://ek9.io/qa/QA0824.html","alternatePhrasings":["What is E07460 FUNCTION_MUST_HAVE_SINGLE_PARAMETER?","Why does my stream filter function fail with parameter count error?","How many parameters can a stream function have in EK9?"],"answer":"EK9 stream operators like filter, uniq, sort, and map require functions with exactly one parameter matching the stream element type.\n\nTHE RULE\nStream pipeline functions must have a single parameter:\n  isPositive() as pure\n    -> amount as Integer       // ONE parameter: correct\n    <- rtn as Boolean: amount > 0\n\nA function with zero or multiple parameters triggers E07460:\n  isInRange() as pure\n    -> lo as Integer           // TWO parameters: wrong\n    -> hi as Integer\n    <- rtn as Boolean: true\n\nWHY THIS RULE\nStream operators call the function once per element, passing the current element as the single argument. A function with 0 or 2+ parameters cannot receive a single stream element.\n\nHOW TO FIX\n- Use a single parameter matching the stream type\n- If you need additional context, use a dynamic function that captures extra values\n\nSee Q812 for stream errors. See Q126 for stream reference.","ek9Example":"defines module qa.streams.function.arity\n\n  defines function\n\n    isPositive() as pure\n      -> amount as Integer\n      <- rtn as Boolean: amount > 0\n\n    isInRange() as pure\n      ->\n        lowerBound as Integer\n        upperBound as Integer\n      <- rtn as Boolean: lowerBound < upperBound\n\n  defines program\n\n    StreamFunctionArityDemo()\n      stdout <- Stdout()\n\n      // === CORRECT: filter with single-parameter predicate ===\n      cat [3, -1, 5, -2, 8]\n        | filter by isPositive\n        > stdout","migrationContext":"Java: Stream.filter() takes Predicate<T> (single arg). Python: filter(func, iterable) passes one item. Rust: .filter(|x| ...) takes one reference. Go: no built-in stream, but range gives one item. EK9: compile-time enforcement that stream functions have exactly one parameter.","keywords":["E07460","arity","filter","function","parameter","single","sort","stream","uniq"],"primaryTopics":["stream function parameter","E07460"],"typicalErrors":[{"error":"E07460","correct":"        | filter by isPositive","incorrect":"        | uniq isInRange","explanation":"Stream uniq requires a function with exactly one parameter matching the stream type. isInRange has two parameters so cannot receive a single stream element. See ek9 -h E07460 for details."}],"companions":[]}
{"id":825,"category":"Code Quality","question":"Why does EK9 reject 'not empty' on a freshly created collection?","url":"https://ek9.io/qa/QA0825.html","alternatePhrasings":["What is E08093 NEVER_EMPTY_CHECK?","Why is my 'not empty' check dead code in EK9?","How does EK9 detect impossible empty checks?"],"answer":"EK9 detects when 'not empty' is checked on a collection that is provably empty. The condition is always false, making the body dead code.\n\nTHE PATTERN\n  items <- List() of String\n  if items not empty       // ALWAYS false — list just created\n    process(items)         // dead code — never executes\n\nThe compiler knows the list was just created with no items added, so 'not empty' can never be true at this point.\n\nVALID USE\nAfter items have been added, the check is meaningful:\n  items <- List() of String\n  items += value\n  if items not empty       // valid — items were added\n    process(items)\n\nSee Q820 for redundant empty checks (E08092). See Q734 for tautology detection.","ek9Example":"defines module qa.quality.never.empty\n\n  defines function\n\n    processIfPresent()\n      -> inputValue as String\n      <- result as String: \"nothing\"\n\n      items <- List() of String\n      items += inputValue\n      if items not empty\n        result: \"has items\"\n\n  defines program\n\n    NeverEmptyDemo()\n      stdout <- Stdout()\n      result <- processIfPresent(\"hello\")\n      stdout.println(`Result: ${result}`)","migrationContext":"Java: no detection. Python: no detection. Rust: no detection. Go: no detection. EK9: compile-time error for provably impossible 'not empty' checks using data flow analysis.","keywords":["E08093","code","collection","dead","dict","empty","list","never","tautology"],"primaryTopics":["never empty check","E08093"],"typicalErrors":[{"error":"E08093","correct":"      items += inputValue\n      if items not empty","incorrect":"      //items += inputValue\n      if items not empty","explanation":"Without adding items, the list is provably empty. 'not empty' is always false — the body is dead code. See ek9 -h E08093 for details."}],"companions":[]}
{"id":826,"category":"Code Quality","question":"Why does EK9 reject override on constructs that cannot override?","url":"https://ek9.io/qa/QA0826.html","alternatePhrasings":["What is E05100 OVERRIDE_INAPPROPRIATE?","Why can't I use override on a program in EK9?","Where is the override keyword not allowed in EK9?"],"answer":"EK9 rejects the override keyword on constructs that do not support inheritance-based overriding. Programs and service methods cannot override anything.\n\nWHERE OVERRIDE IS INVALID\n- Programs: programs are entry points, not extendable\n- Service methods: services do not inherit from other services\n\nWHERE OVERRIDE IS VALID\n- Class methods that replace a parent class method\n- Trait method implementations in classes\n- Function extensions that replace a parent function\n\nWHY THIS MATTERS\nThe override keyword is a contract: 'I intend to replace a parent implementation.' Using it on a construct that has no parent hierarchy is a logic error caught at compile time.\n\nSee Q96 for inheritance. See Q155 for testing.","ek9Example":"defines module qa.quality.override.check\n\n  defines program\n\n    OverrideCheckDemo()\n      stdout <- Stdout()\n      stdout.println(\"Programs cannot use override\")","migrationContext":"Java: @Override on a static method or constructor is a compile error. Kotlin: override keyword validated against parent. C++: override specifier checked. Python: no enforcement. EK9: override validated at SYMBOL_DEFINITION phase.","keywords":["E05100","inappropriate","inheritance","override","program","service"],"primaryTopics":["override inappropriate","E05100"],"typicalErrors":[{"error":"E05100","correct":"    OverrideCheckDemo()","incorrect":"    override OverrideCheckDemo()","explanation":"Programs cannot use override — they are entry points with no parent to override. Remove the override keyword. See ek9 -h E05100 for details."}],"companions":[]}
{"id":827,"category":"Code Quality","question":"Why does EK9 reject the ? modifier on function parameters?","url":"https://ek9.io/qa/QA0827.html","alternatePhrasings":["What is E07300 DECLARED_AS_NULL_NOT_NEEDED?","Why can't I declare a parameter as potentially unset in EK9?","How does EK9 enforce parameter initialisation?"],"answer":"EK9 requires function parameters to always be initialised. Declaring a parameter with ? (potentially unset) is not needed because the caller must always provide a value.\n\nTHE RULE\nParameters cannot use the ? unset modifier:\n  process()\n    -> input as String?    // ERROR: E07300\nParameters are always set by the caller. The ? modifier would be misleading.\n\nTHE FIX\nRemove the ? modifier:\n  process()\n    -> input as String     // CORRECT: always initialised\n\nWHY THIS MATTERS\nEK9 pushes for everything to be initialised. Parameters are provided by callers, so they are always set when the function body executes. Allowing ? on parameters would create false optionality.\n\nSee Q310 for code quality. See Q126 for function patterns.","ek9Example":"defines module qa.quality.null.not.needed\n\n  defines function\n\n    processInput() as pure\n      -> input as String\n      <- rtn as String: `Processed: ${input}`\n\n  defines program\n\n    NullNotNeededDemo()\n      stdout <- Stdout()\n      result <- processInput(\"hello\")\n      stdout.println(result)","migrationContext":"Java: parameters are always non-null unless explicitly @Nullable. Kotlin: parameters can be nullable with ?. Rust: parameters are always initialised. Go: parameters always have zero values. EK9: parameters are always initialised — ? modifier not allowed.","keywords":["E07300","declared","initialised","null","optional","parameter","unset"],"primaryTopics":["declared as null not needed","E07300"],"typicalErrors":[{"error":"E07300","correct":"    -> input as String","incorrect":"    -> input as String?","explanation":"Function parameters are always provided by the caller and cannot be unset. Remove the ? modifier. See ek9 -h E07300 for details."}],"companions":[]}
{"id":828,"category":"Code Quality","question":"Why can't I use sanitized at a call site in EK9?","url":"https://ek9.io/qa/QA0828.html","alternatePhrasings":["What is E07942 SANITIZED_NOT_ALLOWED_AT_CALL_SITE?","Where does sanitized go — declaration or call site?","How do I fix sanitized at call site errors in EK9?"],"answer":"EK9 enforces that the sanitized keyword is only used in parameter declarations, not at call sites. Sanitization is the callee's responsibility, not the caller's.\n\nTHE RULE\nSanitized goes on the parameter declaration:\n  processInput()\n    -> data as sanitized String    // CORRECT: callee declares sanitization\nNot at the call site:\n  processInput(sanitized userInput)  // ERROR: caller cannot specify\n\nWHY THIS MATTERS\nSanitization is a contract between the function and its callers. The function declares that it sanitizes its input — this is enforced at compile time. If callers could specify sanitization, it would be ambiguous: does the caller sanitize, the callee, or both?\n\nEK9's rule: the function that receives the data is responsible for declaring and performing sanitization. The caller just passes the data.\n\nSee Q127 for sanitized parameters. See Q310 for code quality.","ek9Example":"defines module qa.quality.sanitized.callsite\n\n  defines function\n\n    processInput()\n      -> rawInput as sanitized String\n      <- rtn as String: rawInput\n\n  defines program\n\n    SanitizedCallSiteDemo()\n      stdout <- Stdout()\n      userInput <- \"hello <script>alert('xss')</script>\"\n\n      // === CORRECT: just pass the data, callee sanitizes ===\n      result <- processInput(userInput)\n      stdout.println(result)","migrationContext":"Java: no built-in sanitization — developers use @Valid, @Sanitize annotations. Python: no enforcement. Rust: no built-in concept. Go: no enforcement. EK9: sanitized keyword on parameter declarations enforced at compile time, rejected at call sites.","keywords":["E07942","call","input","parameter","sanitized","security","site","validation"],"primaryTopics":["sanitized call site","E07942"],"typicalErrors":[{"error":"E07942","correct":"      result <- processInput(userInput)","incorrect":"      result <- processInput(sanitized userInput)","explanation":"The sanitized keyword belongs on the parameter declaration, not at the call site. The callee decides what to sanitize. See ek9 -h E07942 for details."}],"companions":[]}
{"id":829,"category":"Code Quality","question":"Why does EK9 reject duplicate properties in records with JSON support?","url":"https://ek9.io/qa/QA0829.html","alternatePhrasings":["What is E02020 CANNOT_SUPPORT_TO_JSON_DUPLICATE_PROPERTY_FIELD?","Why can't my child record redeclare a parent property with default operators?","How does property duplication affect JSON serialization in EK9?"],"answer":"When a record uses 'default operator' to auto-generate operators including $$ (toJSON), EK9 requires all property names to be unique across the inheritance hierarchy. Duplicate property names would produce ambiguous JSON keys.\n\nTHE RULE\nIf a child record redeclares a parent property AND uses 'default operator':\n  ParentRecord\n    name as String\n    default operator\n  ChildRecord extends ParentRecord\n    name as String       // ERROR: E02020 — ambiguous JSON key\n    default operator\n\nWHY THIS MATTERS\nThe $$ operator generates JSON like {\"name\": \"value\"}. If both parent and child have 'name', the JSON would have duplicate keys — which is undefined behaviour in JSON.\n\nHOW TO FIX\n- Use a different name in the child: childName as String\n- Or manually implement the $$ operator instead of using default\n\nSee Q819 for duplicate property basics. See Q97 for records.","ek9Example":"defines module qa.quality.duplicate.json\n\n  defines record\n\n    BaseConfig as open\n      label as String: String()\n      default operator ?\n      default operator $\n\n    ChildConfig extends BaseConfig\n      childLabel as String: String()\n      default operator ?\n      default operator $\n\n  defines program\n\n    DuplicateJsonDemo()\n      stdout <- Stdout()\n      child <- ChildConfig()\n      stdout.println($child)","migrationContext":"Java: Jackson serializes duplicate fields from parent+child creating ambiguous JSON. Python: no enforcement. Rust: no inheritance. Go: embedded struct fields can shadow. EK9: compile-time error prevents ambiguous JSON serialization.","keywords":["E02020","duplicate","json","property","record","serialization","toJson"],"primaryTopics":["duplicate JSON property","E02020"],"typicalErrors":[{"error":"E02010","correct":"    ChildConfig extends BaseConfig\n      childLabel as String: String()","incorrect":"    ChildConfig extends BaseConfig\n      label as String: String()","explanation":"The parent BaseConfig already has 'label'. Redeclaring it in a child creates a duplicate property. Use a different name. See ek9 -h E02010 for details."}],"companions":[]}
{"id":830,"category":"Code Quality","question":"Why does EK9 reject non-aggregate types in certain contexts?","url":"https://ek9.io/qa/QA0830.html","alternatePhrasings":["What is E04060 IS_NOT_AN_AGGREGATE_TYPE?","Why can't I use a function where a class is expected in EK9?","What is an aggregate type in EK9?"],"answer":"EK9 distinguishes between aggregate types (class, record, component, trait, service) and non-aggregate types (function, program). Some operations require aggregate types.\n\nAGGREGATE TYPES\nTypes that have properties, methods, and operators:\n  class, record, component, trait, service, enumeration\n\nNON-AGGREGATE TYPES\nCallable units without properties:\n  function, program\n\nWHERE AGGREGATES ARE REQUIRED\n- Stream sort: the items being sorted must be aggregate types with comparison operators\n- Type extensions: extends requires an aggregate\n- Property access: only aggregates have properties\n\nCOMMON MISTAKE\nPassing a function reference where an aggregate instance is expected:\n  cat [myFunction] | sort > result   // ERROR if sorting functions\n\nSee Q97 for class vs record. See Q126 for stream reference.","ek9Example":"defines module qa.quality.aggregate.type\n\n  defines function\n\n    getAlpha()\n      <- rtn <- \"alpha\"\n\n  defines program\n\n    AggregateTypeDemo()\n      stdout <- Stdout()\n\n      collection <- List() of String\n\n      // === CORRECT: sort strings (aggregate type with <=> operator) ===\n      cat [\"alpha\", \"beta\"] | sort > collection\n\n      require collection?\n      stdout.println(\"Sorted collection\")","migrationContext":"Java: everything is a class (functions are objects). Python: functions are objects with attributes. Rust: functions and closures are distinct from structs. Go: functions have no methods. EK9: clear separation — aggregates have properties and operators, functions are pure callables.","keywords":["E04060","aggregate","class","function","record","sort","stream","type"],"primaryTopics":["aggregate type","E04060","not an aggregate"],"typicalErrors":[{"error":"E04060","correct":"      cat [\"alpha\", \"beta\"] | sort > collection","incorrect":"      cat [getAlpha] | sort > collection","explanation":"Stream sort requires aggregate types with comparison operators. Function references are not aggregates and cannot be sorted. See ek9 -h E04060 for details."}],"companions":[]}
{"id":831,"category":"Code Quality","question":"Why can't I use an abstract function as a return type directly in EK9?","url":"https://ek9.io/qa/QA0831.html","alternatePhrasings":["What is E50070 BAD_ABSTRACT_FUNCTION_USE?","Why does EK9 reject returning an abstract function type?","How do I fix abstract function return type errors?"],"answer":"EK9 prevents using abstract function types directly as return types via built-in constructors like Function(). Abstract functions define contracts — they must be implemented concretely.\n\nTHE RULE\nYou cannot return an abstract function type via its constructor:\n  getHandler()\n    <- rtn as Function of (Integer, String): Function  // ERROR\n\nHOW TO FIX\n- Return a concrete implementation of the abstract function\n- Use a dynamic function to create an inline implementation\n- Declare the return type as the abstract function but assign a concrete one\n\nWHY THIS MATTERS\nAbstract functions have no implementation. Constructing one would create a callable with no body — which would fail at runtime. EK9 catches this at compile time.\n\nSee Q821 for abstract type construction. See Q126 for function patterns.","ek9Example":"defines module qa.quality.abstract.function\n\n  defines function\n\n    abstractGreet() as abstract\n      <- rtn as String?\n\n    concreteGreet() is abstractGreet\n      <- rtn <- \"Hello from concrete\"\n\n    getGreeting()\n      <- rtn as String: concreteGreet()\n\n  defines program\n\n    AbstractFunctionDemo()\n      stdout <- Stdout()\n\n      // === CORRECT: use concrete function ===\n      greeting <- getGreeting()\n      stdout.println(greeting)","migrationContext":"Java: cannot instantiate abstract classes or interfaces directly. Python: ABCMeta raises TypeError. Rust: traits cannot be constructed. Go: interfaces have no constructors. EK9: compile-time error for abstract function construction.","keywords":["E50070","abstract","concrete","function","implementation","return","type"],"primaryTopics":["abstract function use","E50070"],"typicalErrors":[{"error":"E50080","correct":"      <- rtn as String: concreteGreet()","incorrect":"      <- rtn as String: abstractGreet()","explanation":"Abstract functions cannot be called directly — they have no implementation. Use a concrete function that extends the abstract one. See ek9 -h E50080 for details."}],"companions":[]}
{"id":832,"category":"Code Quality","question":"Why can't I have a local class with the same name as an imported reference in EK9?","url":"https://ek9.io/qa/QA0832.html","alternatePhrasings":["What is E03010 CONSTRUCT_REFERENCE_CONFLICT?","Why does EK9 reject my class when I also import the same name?","How do I fix a name collision between a local construct and a referenced import?"],"answer":"EK9 prevents any name collision between locally-defined constructs and imported references. Neither takes priority — the compiler rejects the ambiguity immediately with E03010.\n\nTHE RULE\nIf you reference (import) a symbol like 'Helper' from another module, you cannot also define a local class, function, or record called 'Helper' in the same module. The compiler cannot know which one you mean when you write 'Helper()'.\n\nWHY NO PRIORITY RULES\nSome languages let local definitions shadow imports (C#) or let wildcard imports shadow locals (Python's 'from module import *'). Both create subtle bugs where the wrong type is silently used. Java's 'import java.util.*' can silently resolve to the wrong List. EK9 forces you to choose explicitly.\n\nHOW TO FIX\n- Rename your local construct to a unique name\n- Remove the reference if you only need the local version\n- Use fully qualified module::TypeName if both are truly needed\n\nTHIS EXAMPLE\nShows proper module naming with distinct local class names. When importing from another module, always ensure your local constructs have unique names.\n\nSee Q741 for reference conflict overview. See Q119 for constructs overview. See Q13 for package vs module.","ek9Example":"defines module qa.quality.reference.collision\n\n  defines class\n\n    <?-\n      When importing from another module, always use distinct names.\n      If another module exports 'Formatter', name your local version\n      differently to avoid E03010 CONSTRUCT_REFERENCE_CONFLICT.\n    -?>\n    LocalFormatter\n      prefix <- \"[\"\n      suffix <- \"]\"\n\n      default LocalFormatter()\n\n      LocalFormatter()\n        ->\n          p as String\n          s as String\n        prefix :=: p\n        suffix :=: s\n\n      formatText()\n        -> text as String\n        <- rtn as String: `${prefix}${text}${suffix}`\n\n      default operator ?\n\n  defines function\n\n    <?-\n      Similarly, local functions must not share names with imported references.\n    -?>\n    localTransform() as pure\n      -> input as String\n      <- rtn as String: `<${input}>`\n\n  defines program\n\n    ReferenceCollisionDemo()\n      stdout <- Stdout()\n\n      formatter <- LocalFormatter(\"[\", \"]\")\n      if formatter?\n        stdout.println(formatter.formatText(\"hello\"))\n\n      stdout.println(localTransform(\"world\"))","migrationContext":"Java: wildcard imports ('import java.util.*') silently shadow local types — 'which List?' confusion surfaces late in production. Python: 'from module import *' can overwrite locals silently. C#: local types take priority over 'using' imports, masking bugs. Rust: 'use' aliases prevent collisions. EK9: any collision between local and imported is E03010, no shadowing permitted.","keywords":["E03010","ambiguity","collision","conflict","construct","import","local","module","reference","shadow"],"primaryTopics":["module references","E03010","name collision"],"typicalErrors":[{"error":"E01040","correct":"localTransform() as pure","incorrect":"LocalFormatter() as pure","explanation":"Renaming a function to the same name as an existing class creates a duplicate symbol. EK9 rejects any ambiguity between constructs sharing the same name. See ek9 -h E01030 for details."}],"companions":[]}
{"id":833,"category":"Generics","question":"Why can't I use a function type to parameterize a generic that calls constructors?","url":"https://ek9.io/qa/QA0833.html","alternatePhrasings":["What is E06110 CONSTRUCTOR_WITH_FUNCTION_IN_GENERIC?","Why does EK9 reject my generic when I parameterize it with a function?","What are the limitations of using functions with generic types?"],"answer":"EK9 generics discover implicit requirements from their bodies. If a generic uses 'T()' (constructor) or 'arg0 <=> arg1' (comparator), those become requirements on the parameterizing type. Functions cannot satisfy constructor or comparator requirements, so parameterizing such generics with a function triggers E06110.\n\nIMPLICIT CONTRACT DISCOVERY\nWhen you write:\n  Holder of type T\n    createNew() <- rtn as T: T()      // Requires T has a constructor\n    compare() <- rtn as Integer: a <=> b  // Requires T has <=>\nThe compiler discovers that T must have a constructor and a comparator.\n\nFUNCTION LIMITATIONS\nFunctions cannot be constructed with 'T()' — they are not classes. Functions cannot be compared with '<=>'. So a function can only parameterize generics that use minimal capabilities like '?' (isSet).\n\nCONTAINER-STYLE GENERICS WORK\nA generic that only checks 'arg0?' and 'arg1?' works with functions — like List or Optional. These are container-style generics that just hold values.\n\nTHIS EXAMPLE\nShows a container-style generic (SafeHolder) that works with both classes and functions, alongside a concrete class (Counter) that satisfies constructor requirements.\n\nSee Q646 for type inference limits. See Q707 for Consumer/Acceptor. See Q58 for generic functions.","ek9Example":"defines module qa.generics.function.in.generic\n\n  defines function\n\n    <?-\n      Abstract function type for use as a delegate/generic parameter.\n    -?>\n    CheckFn as abstract\n      -> arg0 as Integer\n      <- rtn as Boolean?\n\n    IsPositive() is CheckFn\n      -> arg0 as Integer\n      <- rtn as Boolean: arg0 > 0\n\n  defines class\n\n    Counter\n      count <- 0\n\n      default Counter()\n\n      Counter()\n        -> initial as Integer\n        count :=: initial\n\n      increment()\n        count += 1\n\n      value()\n        <- rtn as Integer: Integer(count)\n\n      default operator ?\n\n    <?-\n      A container-style generic — only uses '?' operator.\n      This works with BOTH classes AND functions.\n    -?>\n    SafeHolder of type T\n\n      default SafeHolder()\n\n      default SafeHolder()\n        -> arg0 as T\n\n      check()\n        ->\n          arg0 as T\n          arg1 as T\n        <-\n          rtn as Boolean: arg0? and arg1?\n\n    <?-\n      Generic that uses T() constructor — NOT safe for functions.\n    -?>\n    ConstructingHolder of type T\n      default ConstructingHolder()\n      default ConstructingHolder()\n        -> arg0 as T\n      check()\n        ->\n          arg0 as T\n          arg1 as T\n        <-\n          rtn as Boolean: arg0? and arg1?\n      createNew()\n        <- rtn as T: T()\n\n    <?-\n      Generic that uses <=> comparator — NOT safe for functions.\n    -?>\n    ComparingHolder of type T\n      default ComparingHolder()\n      default ComparingHolder()\n        -> arg0 as T\n      check()\n        ->\n          arg0 as T\n          arg1 as T\n        <-\n          rtn as Boolean: arg0? and arg1?\n      compare()\n        ->\n          arg0 as T\n          arg1 as T\n        <-\n          rtn as Integer: arg0 <=> arg1\n\n  defines program\n\n    GenericFunctionDemo()\n      stdout <- Stdout()\n\n      //Functions work with container-style generics\n      holder <- SafeHolder() of CheckFn\n      result <- holder.check(IsPositive, IsPositive)\n      stdout.println(`Function holder check: ${result}`)\n\n      //Classes work with any generic\n      counterHolder <- SafeHolder() of Counter\n      classResult <- counterHolder.check(Counter(), Counter(5))\n      stdout.println(`Counter holder check: ${classResult}`)","migrationContext":"Java: type erasure means generic constraint violations appear as ClassCastExceptions at runtime. C++: templates check at instantiation with poor diagnostics. Rust: requires explicit trait bounds ('T: Ord'). EK9: discovers implicit requirements from generic body and validates at parameterization site with specific error codes.","keywords":["E06110","comparator","constraint","constructor","container","function","generic","implicit","isSet","parameterize"],"primaryTopics":["function in generic","E06110","implicit requirements"],"typicalErrors":[{"error":"E06110","correct":"      holder <- SafeHolder() of CheckFn","incorrect":"      holder <- ConstructingHolder() of CheckFn","explanation":"ConstructingHolder uses 'T()' in its body, requiring a constructor. Functions cannot be constructed. Use a container-style generic that only uses '?' (isSet) with functions. See ek9 -h E06110 for details."},{"error":"E06120","correct":"      holder <- SafeHolder() of CheckFn","incorrect":"      holder <- ComparingHolder() of CheckFn","explanation":"ComparingHolder uses 'arg0 <=> arg1' in its body. Functions cannot be compared. Use a generic that only checks isSet with functions. See ek9 -h E06120 for details."}],"companions":[]}
{"id":834,"category":"Classes and OOP","question":"Why can't I use 'default operator <=>' on a record with a function delegate field?","url":"https://ek9.io/qa/QA0834.html","alternatePhrasings":["What is E07210 FUNCTION_DELEGATE_WITH_DEFAULT_OPERATORS?","Why does EK9 reject default comparator on my record with a delegate?","How do I compare records that contain function delegate fields?"],"answer":"EK9 prevents 'default operator <=>' (and similar auto-generated comparison operators) on records that contain function delegate fields. The reason: how do you meaningfully compare two function references? Reference equality is almost never useful.\n\nTHE PROBLEM\nWhen you write 'default operator <=>' the compiler auto-generates comparison by comparing each field. For String or Integer fields this is clear. But for a function delegate field, what does 'delegate1 <=> delegate2' mean? There is no meaningful default comparison.\n\nWHAT STILL WORKS\n- 'default operator ?' (isSet) — always valid, checks if the delegate is assigned\n- Explicit operator implementation — you can write your own comparison that ignores the delegate\n- Records without delegate fields — all default operators work normally\n\nTHIS EXAMPLE\nClass EventHandler wraps a function delegate with an explicit comparator. SimpleRecord (no delegates) uses all default operators freely.\n\nSee Q116 for default operators. See Q96 for operator overview. See Q98 for record operators.","ek9Example":"defines module qa.classesandoop.delegate.defaultoperator\n\n  defines function\n\n    <?-\n      Abstract function type used as a delegate.\n    -?>\n    Handler as abstract\n      -> event as String\n      <- rtn as Boolean?\n\n    LogHandler() is Handler\n      -> event as String\n      <- rtn as Boolean: event?\n\n  defines class\n\n    <?-\n      Class wrapping a function delegate field.\n      Uses explicit comparator since default comparator\n      would not know how to compare function references.\n    -?>\n    EventHandler\n      eventName <- String()\n      handler as Handler?\n\n      default private EventHandler()\n\n      EventHandler()\n        ->\n          n as String\n          h as Handler\n        eventName :=: n\n        handler := h\n\n      fire()\n        <- rtn as Boolean: handler(eventName)\n\n      //Explicit comparator — compares by name, ignoring the delegate\n      operator <=> as pure\n        -> arg0 as EventHandler\n        <- rtn as Integer: eventName <=> arg0.eventName\n\n      override operator ? as pure\n        <- rtn as Boolean: eventName? and handler?\n\n  defines record\n\n    <?-\n      Record without delegates — all default operators work fine.\n    -?>\n    SimpleRecord\n      label <- \"none\"\n\n      SimpleRecord()\n        -> l as String\n        label :=: l\n\n      operator <=> as pure\n        -> arg0 as SimpleRecord\n        <- rtn as Integer: label <=> arg0.label\n\n      default operator\n\n  defines program\n\n    DelegateDemo()\n      stdout <- Stdout()\n\n      eh1 <- EventHandler(\"click\", LogHandler)\n      eh2 <- EventHandler(\"submit\", LogHandler)\n\n      if eh1?\n        comparison <- eh1 <=> eh2\n        stdout.println(`Comparison: ${comparison}`)\n        stdout.println(`Fire: ${eh1.fire()}`)\n\n      simple1 <- SimpleRecord(\"alpha\")\n      simple2 <- SimpleRecord(\"beta\")\n      if simple1?\n        simpleResult <- simple1 <=> simple2\n        stdout.println(`Simple: ${simpleResult}`)","migrationContext":"Java: records with functional interface fields auto-generate equals/hashCode comparing by identity — usually meaningless. Kotlin: data classes with lambda fields use reference equality for lambdas. C#: records include delegates in equality using invocation list — fragile and surprising. EK9: rejects default comparison for delegate-containing records at compile time.","keywords":["E07210","comparator","comparison","default","delegate","field","function","isSet","operator","record"],"primaryTopics":["function delegate operators","E07210","default operator limits"],"typicalErrors":[{"error":"E07210","correct":"      operator <=> as pure\n        -> arg0 as EventHandler\n        <- rtn as Integer: eventName <=> arg0.eventName","incorrect":"      default operator <=>","explanation":"Record has a function delegate field — 'default operator <=>' cannot be auto-generated because comparing function references has no meaningful semantics. Use 'default operator ?' (always valid) or implement comparison explicitly. See ek9 -h E07210 for details."},{"error":"E07210","correct":"      override operator ? as pure\n        <- rtn as Boolean: eventName? and handler?","incorrect":"      default operator","explanation":"Cannot auto-generate all default operators for records with delegates. 'default operator' would include comparators which cannot compare function references. Implement operators explicitly. See ek9 -h E07210 for details."}],"companions":[]}
{"id":835,"category":"Classes and OOP","question":"Why can't I provide a signature or body when using 'default operator' in EK9?","url":"https://ek9.io/qa/QA0835.html","alternatePhrasings":["What is E07230 DEFAULT_WITH_OPERATOR_SIGNATURE?","Why does 'default operator >= as pure' fail to compile?","What does 'default' mean on an operator and why can't I add parameters?"],"answer":"The 'default' keyword on an operator means 'compiler, auto-generate this operator from the existing fields and prerequisite operators'. Adding a signature, modifiers like 'as pure', parameters, or a body contradicts this — you are simultaneously asking the compiler to generate it AND manually specifying it.\n\nTHE RULE\n- 'default operator <=>' — valid, compiler generates comparator\n- 'default operator' — valid, compiler generates ALL supported operators\n- 'default operator >= as pure' — ERROR E07230, contradictory\n- 'default operator ? as pure <- rtn as Boolean: x?' — ERROR E07230, contradictory\n\nWHY IT IS AN ERROR\nIf you want auto-generation, write 'default operator <name>' with nothing else. If you want a custom implementation, write the full operator without 'default'. Mixing the two indicates a misunderstanding of what 'default' means.\n\nPREREQUISITE OPERATORS\nSome default operators require other operators to exist first. For example, 'default operator >=' needs the comparator '<=>'. You can provide '<=>'' explicitly and default the rest.\n\nTHIS EXAMPLE\nThe Shape record provides an explicit comparator, then uses 'default operator' to auto-generate all remaining supported operators (>=, <=, >, <, ==, <>, $, #?).\n\nSee Q116 for default operator overview. See Q96 for operator semantics. See Q774 for abstract constructor.","ek9Example":"defines module qa.classesandoop.default.operator.signature\n\n  defines record\n\n    Shape\n      name <- String()\n      sides <- 0\n\n      Shape()\n        ->\n          n as String\n          s as Integer\n        name :=: n\n        sides :=: s\n\n      //Explicit comparator — prerequisite for defaulting >=, <=, etc.\n      operator <=> as pure\n        -> arg0 as Shape\n        <- rtn as Integer: sides <=> arg0.sides\n\n      //Auto-generate ALL remaining supported operators\n      default operator\n\n  defines class\n\n    Config\n      label <- \"default\"\n      priority <- 0\n\n      default Config()\n\n      Config()\n        ->\n          l as String\n          p as Integer\n        label :=: l\n        priority :=: p\n\n      describe()\n        <- rtn as String: `${label} (priority ${priority})`\n\n      //Explicit comparator\n      operator <=> as pure\n        -> arg0 as Config\n        <- rtn as Integer: priority <=> arg0.priority\n\n      //Selective default — just the isSet operator\n      default operator ?\n\n  defines program\n\n    DefaultOperatorDemo()\n      stdout <- Stdout()\n\n      triangle <- Shape(\"triangle\", 3)\n      square <- Shape(\"square\", 4)\n\n      if triangle?\n        stdout.println(`Triangle >= Square: ${triangle >= square}`)\n        stdout.println(`Triangle == Square: ${triangle == square}`)\n        stdout.println(\"Triangle $: \" + $triangle)\n\n      c1 <- Config(\"high\", 10)\n      c2 <- Config(\"low\", 1)\n      if c1?\n        stdout.println(c1.describe())\n        stdout.println(`c1 > c2: ${c1 <=> c2 > 0}`)","migrationContext":"Java: no operator overloading or auto-generation. Kotlin: data class generates all-or-nothing. Rust: #[derive(PartialEq, Hash)] is selective auto-generation. EK9: 'default operator' is selective, but 'default' and manual specification are mutually exclusive — prevents contradictory declarations.","keywords":["E07230","auto-generate","body","contradictory","default","modifier","operator","prerequisite","pure","signature"],"primaryTopics":["default operator","E07230","auto-generation rules"],"typicalErrors":[{"error":"E07230","correct":"      //Auto-generate ALL remaining supported operators\n      default operator","incorrect":"      //Auto-generate ALL remaining supported operators\n      default operator >= as pure\n        -> arg0 as Shape\n        <- rtn as Boolean: false","explanation":"Cannot provide a signature or body with 'default operator'. The keyword 'default' means the compiler generates it. Either remove 'default' and write the operator manually, or remove the signature/body. See ek9 -h E07230 for details."},{"error":"E07230","correct":"      default operator ?","incorrect":"      default operator ? as pure\n        <- rtn as Boolean: name?","explanation":"Providing 'as pure' modifier and a body with 'default' is contradictory. Use 'default operator ?' alone for auto-generation, or remove 'default' and write the full operator. See ek9 -h E07230 for details."}],"companions":[]}
{"id":836,"category":"Control Flow","question":"Why can't I use compound assignment like '+=' in a switch pre-flow in EK9?","url":"https://ek9.io/qa/QA0836.html","alternatePhrasings":["What is E07340 PRE_FLOW_SYMBOL_NOT_RESOLVED?","Why does 'switch record.prop += 3' fail in EK9?","What assignment forms are valid in switch pre-flow?"],"answer":"EK9 switch pre-flow only accepts declarations ('<-') or simple assignments (':='). Compound assignment operators like '+=' on property access are rejected because the control value is ambiguous.\n\nSee Q69 for multi-case switch. See Q144 for no break/continue/return.","ek9Example":"defines module qa.controlflow.switch.preflow\n\n  defines record\n    Scoring\n      prop1 <- 0\n\n      Scoring()\n        -> initial as Integer\n        prop1 :=: initial\n\n      override operator ? as pure\n        <- rtn as Boolean: prop1?\n\n  defines function\n\n    classifyScore()\n      <- rtn as String: String()\n\n      scoring <- Scoring(5)\n      result <- 0\n\n      switch controlScore <- scoring.prop1 + 3 with controlScore\n        case > 1\n          result += 6\n        default\n          result += 10\n\n      rtn: $result\n\n  defines program\n\n    SwitchPreFlowDemo()\n      stdout <- Stdout()\n      stdout.println(classifyScore())","migrationContext":"Java: switch has no pre-flow. C: assignment in switch is bad practice. Kotlin: when has no pre-flow. EK9: pre-flow restricted to declarations and simple assignments.","keywords":["E07340","assignment","compound","declaration","pre-flow","switch"],"primaryTopics":["switch pre-flow","E07340","compound assignment"],"typicalErrors":[{"error":"E07340","correct":"      switch controlScore <- scoring.prop1 + 3 with controlScore","incorrect":"      switch scoring.prop1 += 3","explanation":"Compound assignment on a property in switch pre-flow has ambiguous control semantics. Use a declaration instead. See ek9 -h E07340 for details."}],"companions":[]}
{"id":837,"category":"Control Flow","question":"Can I use a guard (including '?=') inside a switch or loop expression in EK9?","url":"https://ek9.io/qa/QA0837.html","alternatePhrasings":["Do guards work in switch/for/while/try expressions?","What does 'result <- switch temp ?= getValue() with temp' do?","Why does a guard in an expression need an initialised return (E08050)?"],"answer":"Yes. A body-skipping guard ('<-' declaration, '?=' guarded assignment, ':=?' assign-if-unset) works in an expression-form switch/for/while/do-while/try, just as in statement form. The guard gates the whole construct: if the guarded value is unset the body is skipped, and the left-hand side takes the returning variable's INITIAL value - 'the initialiser is the guard's else'.\n\nBECAUSE OF THAT, THE RETURN MUST BE INITIALISED\nDeclare the return with an initial value ('<- rtn as T: default'). If you declare it uninitialised ('<- rtn as T?') and set it only inside the (skippable) body, the compiler rejects it with E08050 RETURN_NOT_ALWAYS_INITIALISED, because the skip path would leave the LHS unset.\n\nTHIS EXAMPLE\nA switch expression with a '?=' guard and an initialised return. The guard sets 'temperature', the switch runs, and 'resultText' gets a case value; had the guard left 'temperature' unset, 'resultText' would be the initial \"Unknown\".\n\nSee Q759 for guard patterns. See Q69 for multi-case switch.","ek9Example":"defines module qa.controlflow.guard.expression\n\n  defines function\n\n    currentTemperature() as pure\n      -> country as String\n      <- temperature as Integer?\n\n      if country == \"GB\"\n        temperature :=? 18\n      else if country == \"DE\"\n        temperature :=? 35\n      else\n        temperature :=? 25\n\n  defines program\n\n    GuardDemo()\n      stdout <- Stdout()\n\n      //Valid: a '?=' guard in a switch expression. The guard gates the switch; if 'temperature' were left\n      //unset the body would be skipped and 'resultText' would take the return's initial value (\"Unknown\").\n      temperature as Integer?\n      resultText <- switch temperature ?= currentTemperature(\"GB\") with temperature\n        <- result as String: \"Unknown\"\n        case < 12\n          result: \"Cold\"\n        case < 25\n          result: \"Moderate\"\n        default\n          result: \"Warm\"\n\n      stdout.println(resultText)","migrationContext":"Kotlin 'when' and Rust 'match' are expressions but have no guard-gating pre-flow. EK9 unifies it: the same guard syntax works in statement AND expression forms, and the mandatory return initialiser is the value the LHS receives when the guard skips.","keywords":["E08050","conditional","expression","guard","guarded assignment","initialised return","switch"],"primaryTopics":["guard in expression","return must be initialised","E08050"],"typicalErrors":[{"error":"E08050","correct":"        <- result as String: \"Unknown\"","incorrect":"        <- result as String?","explanation":"In an expression form, a guard can skip the body, so the return must be initialised at declaration. Give it a default ('<- result as String: \"Unknown\"'); an uninitialised '<- result as String?' set only in the body is E08050 RETURN_NOT_ALWAYS_INITIALISED."}],"companions":[]}
{"id":838,"category":"Classes and OOP","question":"Why can't I use 'with application of' on a regular class method in EK9?","url":"https://ek9.io/qa/QA0838.html","alternatePhrasings":["What is E07360 APPLICATION_SELECTION_INVALID?","Where can I use 'with application of' in EK9?","How does application selection work in EK9 programs?"],"answer":"The 'with application of' syntax is only valid on programs — it tells the EK9 runtime which application context to inject dependencies from. Using it on a regular class method is an error because class methods do not participate in application-level dependency injection.\n\nTHE RULE\n- Programs CAN use 'with application of <name>' to select a DI context\n- Classes, records, functions, and components CANNOT use this syntax\n- Application selection is a program-level concern, not a method-level concern\n\nWHY ONLY PROGRAMS\nPrograms are entry points — they are the top of the dependency injection graph. The 'with application of' annotation tells the runtime which registered application provides the component bindings. Regular methods receive their dependencies through constructor injection or component fields, not through application selection.\n\nTHIS EXAMPLE\nShows a program using 'with application of' to select an application context. The Logger component receives its dependency through the DI container, not through application selection on methods.\n\nSee Q111 for components. See Q227 for compile-time DI. See Q231 for program-application linking.","ek9Example":"defines module qa.classesandoop.application.selection\n\n  defines component\n\n    Logger\n      prefix <- \"INFO\"\n\n      default Logger()\n\n      Logger()\n        -> p as String\n        prefix :=: p\n\n      log()\n        -> message as String\n        stdout <- Stdout()\n        stdout.println(`[${prefix}] ${message}`)\n\n      default operator ?\n\n  defines class\n\n    Processor\n      name <- \"default\"\n\n      default Processor()\n\n      Processor()\n        -> n as String\n        name :=: n\n\n      process()\n        <- rtn as String: `Processed by ${name}`\n\n      default operator ?\n\n  defines program\n\n    <?-\n      Programs are entry points and CAN use 'with application of'.\n      This is where DI context selection belongs.\n    -?>\n    ProcessorDemo()\n      stdout <- Stdout()\n\n      processor <- Processor(\"main\")\n      if processor?\n        stdout.println(processor.process())\n\n      logger <- Logger(\"DEBUG\")\n      if logger?\n        logger.log(\"Application started\")","migrationContext":"Java Spring: @SpringBootApplication on the main class, not on methods. Python Django: DJANGO_SETTINGS_MODULE at application level. Rust: no DI framework in language. Kotlin: Dagger @Component on application class. Go: wire at application level. EK9: 'with application of' on programs only — class methods use constructor injection.","keywords":["E07360","application","component","context","dependency","entry","injection","method","program","selection"],"primaryTopics":["application selection","E07360","program-level DI"],"typicalErrors":[{"error":"E07360","correct":"      process()\n        <- rtn as String: `Processed by ${name}`","incorrect":"      process() with application of myApp\n        <- rtn as String: `Processed by ${name}`","explanation":"The 'with application of' syntax is only valid on programs, not on class methods. Programs are entry points for DI. Class methods receive dependencies through constructor injection. See ek9 -h E07360 for details."},{"error":"E07360","correct":"      log()\n        -> message as String","incorrect":"      log() with application of myApp\n        -> message as String","explanation":"'with application of' cannot be used on component methods. Application selection belongs on programs only. See ek9 -h E07360 for details."}],"companions":[]}
{"id":839,"category":"Operators and Expressions","question":"Why must the promote operator #^ return a different type than the class?","url":"https://ek9.io/qa/QA0839.html","alternatePhrasings":["What triggers E07420 MUST_NOT_RETURN_SAME_TYPE?","Why does my promote operator fail with same type error?","How do I correctly implement the #^ promote operator in EK9?"],"answer":"The promote operator #^ converts an object to a DIFFERENT type. Returning the same type as the enclosing class is not promotion — it is a copy or identity, which is meaningless.\n\nWHAT PROMOTE DOES\nPromotion converts one type to another:\n  Temperature -> String (displaying degrees)\n  Distance -> Float (extracting raw measurement)\n  Score -> Integer (extracting numeric score)\n\nCORRECT PATTERN\n  operator #^ as pure\n    <- rtn as String: `${degrees} degrees`\nThe return type (String) differs from the class (Temperature).\n\nINCORRECT PATTERN\n  operator #^ as pure\n    <- rtn as C5: this\nReturning the SAME type (C5) from #^ on class C5 triggers E07420. This is not promotion.\n\nUSAGE\n  reading <- Temperature(98.6)\n  promoted <- #^reading  // promoted is now a String\n\nSee Q238 for operator overview. See Q242 for conversion operators.","ek9Example":"defines module qa.operators.promote.different.type\n\n  defines class\n\n    <?-\n      Temperature with promote operator returning String (a different type).\n      This is the correct pattern for promotion.\n    -?>\n    Temperature\n      degrees <- 0.0\n\n      Temperature()\n        -> d as Float\n        degrees :=: d\n\n      //Promote to String — returns DIFFERENT type (correct)\n      operator #^ as pure\n        <- rtn as String: `${degrees} degrees`\n\n      default operator ?\n\n  defines program\n\n    PromoteDemo()\n      stdout <- Stdout()\n      reading <- Temperature(98.6)\n      promoted <- #^reading\n      stdout.println(promoted)","migrationContext":"Java: No promote operator. Implicit conversions via widening (int to long). Explicit casts for narrowing. Python: No operator-based promotion; uses __int__(), __float__(), __str__() dunder methods with no return type enforcement. Rust: From/Into traits enforce different types by design. Kotlin: toInt(), toString() methods with no operator syntax. EK9: #^ operator with compile-time enforcement that return type differs from enclosing class.","keywords":["#^","E07420","conversion","different","operator","promote","return","type"],"primaryTopics":["promote operator","E07420","type conversion"],"typicalErrors":[{"error":"E07420","correct":"      operator #^ as pure\n        <- rtn as String: `${degrees} degrees`","incorrect":"      operator #^ as pure\n        <- rtn as Temperature: this","explanation":"The promote operator #^ must return a DIFFERENT type than the enclosing class. Returning Temperature from a Temperature class is identity, not promotion. Return String, Integer, Float, or another distinct type. See ek9 -h E07420 for details."}],"companions":[]}
{"id":840,"category":"Generics","question":"Why does passing the wrong type to a function cause E06270 in EK9?","url":"https://ek9.io/qa/QA0840.html","alternatePhrasings":["What triggers E06270 PARAMETER_MISMATCH?","Why can't I pass Integer where String is expected in EK9?","How do I convert types explicitly in EK9?"],"answer":"EK9 does NOT auto-convert between incompatible types. Passing an Integer where a String is expected triggers E06270 PARAMETER_MISMATCH.\n\nNO IMPLICIT CONVERSION\nUnlike Java (which auto-boxes and calls toString()), EK9 requires explicit type conversion:\n  acceptsString(123)     // ERROR E06270 — Integer is not String\n  acceptsString($123)    // CORRECT — $ converts Integer to String\n\nTHE $ OPERATOR\nThe $ operator converts any type to its String representation:\n  age <- 25\n  label <- $age           // \"25\" as String\n\nINSIDE BACKTICK STRINGS\nUse ${expression} for interpolation:\n  greeting <- `Age is ${age}`\n\nFUNCTION PARAMETERS MUST MATCH\n  showName(name as String) — expects String\n  showName(\"Steve\")       — correct\n  showName(42)            — ERROR E06270\n  showName($42)           — correct (converts to \"42\")\n\nSee Q194 for generic type basics. See Q238 for operator overview.","ek9Example":"defines module qa.genericsdeep.parameter.type.mismatch\n\n  defines function\n\n    acceptsString() as pure\n      -> phrase as String\n      <- rtn as String: phrase\n\n    acceptsInteger() as pure\n      -> amount as Integer\n      <- rtn as Integer: amount\n\n  defines program\n\n    ParameterTypeDemo()\n      stdout <- Stdout()\n\n      // === CORRECT: matching types ===\n      greeting <- acceptsString(\"Hello EK9\")\n      stdout.println(greeting)\n\n      score <- acceptsInteger(42)\n      stdout.println($score)\n\n      // === CORRECT: explicit $ conversion ===\n      converted <- acceptsString($score)\n      stdout.println(converted)\n\n      // === CORRECT: interpolation inside backticks ===\n      message <- `Score is ${score}`\n      stdout.println(message)","migrationContext":"Java: Auto-boxes primitives and calls toString() implicitly in string concatenation. Integer passed to String parameter causes compile error but string concat works. Python: Dynamically typed, type mismatches caught only at runtime. Rust: No implicit conversions, requires .to_string() or Into trait. Kotlin: Requires explicit .toString(), similar to EK9. EK9: No implicit conversions, uses $ operator for string conversion.","keywords":["E06270","conversion","dollar","explicit","mismatch","parameter","string","type"],"primaryTopics":["parameter type mismatch","E06270","explicit type conversion"],"typicalErrors":[{"error":"E06270","correct":"      converted <- acceptsString($score)","incorrect":"      converted <- acceptsString(score)","explanation":"Integer cannot be implicitly converted to String. Use the $ operator to explicitly convert: $123 produces the String \"123\". See ek9 -h E06270 for details."}],"companions":[]}
{"id":841,"category":"Generics","question":"Why can't I instantiate an abstract generic type like Iterator of String?","url":"https://ek9.io/qa/QA0841.html","alternatePhrasings":["What triggers E10030 CONSTRUCTOR_USED_ON_ABSTRACT_TYPE?","Why does Iterator() of String fail in EK9?","How do I get an iterator from a collection in EK9?"],"answer":"Parameterizing an abstract generic type does NOT make it concrete. Iterator, Supplier, Predicate, Consumer, and other abstract function types remain abstract after parameterization.\n\nWHY IT FAILS\n  notAllowed <- Iterator() of String  // ERROR E10030\nIterator is abstract. Adding 'of String' specifies the type parameter but does not provide an implementation. You cannot call a constructor on an abstract type.\n\nCORRECT ALTERNATIVES\n\n1. Get an iterator from a concrete collection:\n  names <- List() of String\n  names += \"Steve\"\n  iter <- names.iterator()\n\n2. Use a dynamic function for abstract function types:\n  greeter <- () is Supplier of String as function (rtn: \"Hello\")\n\n3. Extend with a concrete implementation:\n  Define a class that extends the abstract type and provides method bodies.\n\nTHIS APPLIES TO ALL ABSTRACT GENERICS\nIterator, Supplier, Consumer, Producer, Predicate, Acceptor, Function, Comparator, UnaryOperator, and all Bi- variants.\n\nSee Q642 for generic constructor inference. See Q194 for generic class basics.","ek9Example":"defines module qa.genericsdeep.abstract.generic.instantiation\n\n  defines program\n\n    AbstractGenericDemo()\n      stdout <- Stdout()\n\n      // === CORRECT: get iterator from a concrete List ===\n      names <- List() of String\n      names += \"Steve\"\n      names += \"Limb\"\n\n      iter <- names.iterator()\n      stdout.println(`Iterator obtained: ${iter?}`)\n\n      // === CORRECT: use concrete implementation for abstract function types ===\n      names += \"EK9\"\n      stdout.println(`List has ${length names} items`)","migrationContext":"Java: Same rule — 'new Iterator<String>()' fails. Java allows anonymous classes: 'new Iterator<String>() { ... }'. Python: abc module catches abstract instantiation at runtime only. Kotlin: Same compile-time enforcement; uses SAM conversions for functional interfaces. Rust: Traits cannot be instantiated, only implemented. EK9: E10030 at compile time for all abstract generic types.","keywords":["E10030","abstract","collection","concrete","constructor","generic","instantiation","iterator"],"primaryTopics":["abstract generic instantiation","E10030","iterator from collection"],"typicalErrors":[{"error":"E10030","correct":"      iter <- names.iterator()","incorrect":"      iter <- Iterator() of String","explanation":"Iterator is abstract. Parameterizing it with 'of String' does not make it concrete. Get an iterator from a concrete collection like List instead. See ek9 -h E10030 for details."}],"companions":[]}
{"id":842,"category":"Streams and Pipelines","question":"Why does streaming non-function values through call fail with E04040?","url":"https://ek9.io/qa/QA0842.html","alternatePhrasings":["What triggers E04040 TYPE_MUST_BE_FUNCTION?","Why can't I use cat [1, 2, 3] | call in EK9?","What types can flow through call in an EK9 stream?"],"answer":"The stream call and async operators execute function delegates flowing through the pipeline. If the stream contains non-function values (integers, strings), they cannot be 'called' and the compiler rejects with E04040.\n\nSee Q813 for call/async rules.","ek9Example":"defines module qa.streams.call.requires.function\n\n  defines function\n\n    getGreeting()\n      <- rtn <- \"Hello\"\n\n    getFarewell()\n      <- rtn <- \"Goodbye\"\n\n  defines class\n    StreamSink\n      received <- String()\n      operator |\n        -> item as String\n        if item?\n          received: String(item)\n      override operator ? as pure\n        <- rtn as Boolean: received?\n\n  defines function\n\n    StreamCallDemo()\n      collector <- StreamSink()\n      cat [getGreeting, getFarewell] | call > collector\n      require collector?","migrationContext":"Java: no built-in call operator. Python: map(func, iterable). EK9: call/async require function delegates as stream elements.","keywords":["E04040","call","delegate","function","pipeline","stream","type"],"primaryTopics":["stream call type requirement","E04040","TYPE_MUST_BE_FUNCTION"],"typicalErrors":[{"error":"E04040","correct":"      cat [getGreeting, getFarewell] | call > collector","incorrect":"      cat [1, 2, 3] | call > collector","explanation":"Stream call requires function delegates, not integer values. See ek9 -h E04040 for details."}],"companions":[]}
{"id":843,"category":"Streams and Pipelines","question":"Why must functions used with stream call/async return a value?","url":"https://ek9.io/qa/QA0843.html","alternatePhrasings":["What triggers E07490 FUNCTION_MUST_RETURN_VALUE?","Why can't I use void functions with call in EK9 streams?","What function signature does stream call require?"],"answer":"Functions used with call/async must return a value — the return value becomes the next element downstream. A void function produces nothing.\n\nSee Q813 for call/async rules. See Q842 for function type requirement.","ek9Example":"defines module qa.streams.function.must.return\n\n  defines function\n\n    getFirstName()\n      <- rtn <- \"Steve\"\n\n    getLastName()\n      <- rtn <- \"Limb\"\n\n  defines class\n    StreamSink\n      received <- String()\n      operator |\n        -> item as String\n        if item?\n          received: String(item)\n      override operator ? as pure\n        <- rtn as Boolean: received?\n\n  defines function\n\n    StreamReturnDemo()\n      collector <- StreamSink()\n      cat [getFirstName] | call > collector\n      require collector?","migrationContext":"Java: Supplier returns T, Runnable returns void. Python: map expects return values. EK9: call/async require supplier-style functions.","keywords":["E07490","call","function","pipeline","return","stream","supplier","void"],"primaryTopics":["stream function return","E07490","FUNCTION_MUST_RETURN_VALUE"],"typicalErrors":[{"error":"E07490","correct":"    getFirstName()\n      <- rtn <- \"Steve\"","incorrect":"    getFirstName()\n      stdout <- Stdout()\n      stdout.println(\"logged\")","explanation":"Functions used with stream call must return a value. Void functions produce nothing. See ek9 -h E07490 for details."}],"companions":[]}
{"id":844,"category":"Streams and Pipelines","question":"Why must functions used with stream call/async take no arguments?","url":"https://ek9.io/qa/QA0844.html","alternatePhrasings":["What triggers E06310 REQUIRE_NO_ARGUMENTS?","Why can't I stream functions with parameters through call?","What is the correct function shape for stream call?"],"answer":"Functions used with call/async must take ZERO arguments. The stream calls them without parameters.\n\nSee Q813 for call/async rules. See Q843 for return requirement.","ek9Example":"defines module qa.streams.call.no.arguments\n\n  defines function\n\n    getTimestamp()\n      <- rtn <- \"2026-03-26\"\n\n    getHostname()\n      <- rtn <- \"ek9-server\"\n\n  defines class\n    StreamSink\n      received <- String()\n      operator |\n        -> item as String\n        if item?\n          received: String(item)\n      override operator ? as pure\n        <- rtn as Boolean: received?\n\n  defines function\n\n    StreamNoArgsDemo()\n      collector <- StreamSink()\n      cat [getTimestamp] | call > collector\n      require collector?","migrationContext":"Java: Supplier has get() with no arguments. Python: zero-arg callables. EK9: call/async require zero-argument supplier functions.","keywords":["E06310","arguments","call","function","pipeline","stream","supplier","zero-arg"],"primaryTopics":["stream call arguments","E06310","REQUIRE_NO_ARGUMENTS"],"typicalErrors":[{"error":"E06310","correct":"    getTimestamp()\n      <- rtn <- \"2026-03-26\"","incorrect":"    getTimestamp()\n      -> inputArg as String\n      <- rtn <- \"2026-03-26\"","explanation":"Functions used with stream call must take no arguments. See ek9 -h E06310 for details."}],"companions":[]}
{"id":845,"category":"Web Services","question":"Why must all EK9 service methods return HTTPResponse?","url":"https://ek9.io/qa/QA0845.html","alternatePhrasings":["What triggers E07800 SERVICE_MISSING_RETURN?","Why can't my service method omit a return type?","What return type do EK9 service methods require?"],"answer":"ALL EK9 service methods must return HTTPResponse. Services are HTTP endpoints — only HTTPResponse can express status codes, headers, content type, and body.\n\nSee Q659 for service return types. See Q684 for URI paths.","ek9Example":"defines module qa.webservicesdeep.service.return.httpresponse\n\n  defines service\n\n    HealthEndpoint :/health open\n\n      healthCheck() as GET for :/status\n        <- response as HTTPResponse: () with trait HTTPResponse\n          override content()\n            <- rtn as String: `{\"status\": \"healthy\"}`\n          override status() as pure\n            <- rtn as Integer: 200\n          override contentType() as pure\n            <- rtn as String: \"application/json\"\n          override cacheControl() as pure\n            <- rtn as String: \"no-cache\"\n          override contentLanguage() as pure\n            <- rtn as String: \"en\"\n          default operator ?\n\n  defines application\n\n    HealthApp\n      register HealthEndpoint()\n\n  defines program\n\n    ServiceReturnDemo()\n      stdout <- Stdout()\n      stdout.println(\"Service methods must return HTTPResponse\")","migrationContext":"Java Spring: controllers return any type. C# ASP.NET: IActionResult or POCO. Go: writes to ResponseWriter. EK9: mandatory HTTPResponse.","keywords":["E07800","HTTPResponse","endpoint","return","service"],"primaryTopics":["service return type","E07800","SERVICE_MISSING_RETURN"],"typicalErrors":[],"companions":[]}
{"id":846,"category":"Web Services","question":"Why can't I use Float as a service method parameter in EK9?","url":"https://ek9.io/qa/QA0846.html","alternatePhrasings":["What triggers E07760 SERVICE_INCOMPATIBLE_PARAM_TYPE_NON_REQUEST?","What parameter types are valid for EK9 service methods?","Why does EK9 restrict service parameter types?"],"answer":"EK9 service method parameters are automatically parsed from HTTP request strings (query params, path params). Only types that can be safely parsed from strings are allowed. Float and Money are excluded because HTTP string representation loses precision.\n\nALLOWED TYPES\nInteger, String, Date, Duration, DateTime, Time, Millisecond — all can be unambiguously parsed from a string.\n\nEXCLUDED TYPES\nFloat — string representation is lossy (0.1 cannot be exactly represented)\nMoney — financial precision requires exact decimal parsing, not HTTP string conversion\n\nCORRECT PATTERN\nUse Integer for numeric parameters, String for text:\n  findById() as GET for :/{userId}\n    -> userId as Integer\n\nSee Q657 for URI mapping. See Q845 for return type requirement.","ek9Example":"defines module qa.webdeep.service.parameter.types\n\n  defines constant\n\n    JSON_TYPE <- \"application/json\"\n\n  defines service\n\n    <?-\n      Service with valid parameter types.\n      Parameters are parsed from HTTP request strings.\n    -?>\n    UserService :/users open\n\n      findById() as GET for :/{userId}\n        -> userId as Integer\n        <- response as HTTPResponse: (capturedId: userId) with trait HTTPResponse\n          override content()\n            <- rtn as String: `{\"userId\": ${capturedId}}`\n          override status() as pure\n            <- rtn as Integer: 200\n          override contentType() as pure\n            <- rtn as String: JSON_TYPE\n          override cacheControl() as pure\n            <- rtn as String: \"no-cache\"\n          override contentLanguage() as pure\n            <- rtn as String: \"en\"\n          default operator ?\n\n      findByName() as GET for :/{userName}/profile\n        -> userName as String\n        <- response as HTTPResponse: (capturedName: userName) with trait HTTPResponse\n          override content()\n            <- rtn as String: `{\"name\": \"${capturedName}\"}`\n          override status() as pure\n            <- rtn as Integer: 200\n          override contentType() as pure\n            <- rtn as String: JSON_TYPE\n          override cacheControl() as pure\n            <- rtn as String: \"max-age=60\"\n          override contentLanguage() as pure\n            <- rtn as String: \"en\"\n          default operator ?\n\n  defines application\n\n    UserApp\n      register UserService()\n\n  defines program\n\n    ServiceParamDemo()\n      stdout <- Stdout()\n      stdout.println(\"Valid service parameter types: Integer, String, Date, Time\")\n      stdout.println(\"Invalid: Float (precision loss), Money (requires exact decimal)\")","migrationContext":"Java Spring: @RequestParam binds to any type with a converter — no compile-time restriction. C# ASP.NET: model binding to complex types at runtime. Go: manual string parsing from http.Request. Python Flask: parameter converters registered at runtime. EK9: compile-time restriction to safely-parseable types prevents silent precision loss.","keywords":["E07760","Float","HTTP","Integer","parameter","parse","service","type"],"primaryTopics":["service parameter types","E07760","SERVICE_INCOMPATIBLE_PARAM_TYPE_NON_REQUEST"],"typicalErrors":[{"error":"E07760","correct":"      findById() as GET for :/{userId}\n        -> userId as Integer","incorrect":"      findByPrice() as GET for :/{price}\n        -> price as Float","explanation":"Float is not a valid service parameter type. HTTP string representation loses floating-point precision. Use Integer or String instead, and parse within the method if needed. See ek9 -h E07760 for details."}],"companions":[]}
{"id":847,"category":"Security and Sanitization","question":"Where can the sanitized modifier be used in EK9?","url":"https://ek9.io/qa/QA0847.html","alternatePhrasings":["What is E07920 in EK9?","Can I mark a field as sanitized?","Why can only parameters be sanitized?","Where is sanitized valid in EK9?"],"answer":"The 'sanitized' modifier can only be applied to incoming function or method parameters. Fields, return values, and local variables cannot be marked as sanitized.\n\nWHY ONLY PARAMETERS\nSanitization is a boundary concern. Input enters the system through function and method parameters. Once input has been sanitized at the boundary, the sanitized value can be stored normally. Marking fields or locals as sanitized would be meaningless because they are already inside the trusted boundary.\n\nCORRECT PATTERN\nApply sanitized to the incoming parameter, then store the result:\n  processInput() as pure\n    -> userInput as sanitized String\n    <- rtn as String: userInput\n\nINCORRECT PATTERNS\n- Field: name as sanitized String (E07920)\n- Return: <- rtn as sanitized String (E07920)\n- Local: cleaned as sanitized String (E07920)\n\nSee Q844 for sanitized overview. See Q845 for sanitized with records.","ek9Example":"defines module qa.sanitizeddeep.parameter.only\n\n  defines function\n\n    <?-\n      Correct use of sanitized on an incoming parameter.\n      The sanitized value is returned as a normal String.\n    -?>\n    processInput() as pure\n      -> userInput as sanitized String\n      <- rtn as String: userInput\n\n  defines program\n\n    SanitizedDemo()\n      stdout <- Stdout()\n\n      cleaned <- processInput(\"hello<script>\")\n      stdout.println(cleaned)","migrationContext":"Java: no language-level sanitization (relies on libraries like OWASP). Python: no sanitization concept. Rust: no built-in sanitization. Go: no sanitization. EK9: compile-time sanitized modifier on parameters only, E07920 if misplaced.","keywords":["E07920","boundary","field","input","local","parameter","return","sanitized","security","validation"],"primaryTopics":[],"typicalErrors":[{"error":"E07920","correct":"    processInput() as pure\n      -> userInput as sanitized String\n      <- rtn as String: userInput","incorrect":"    SomeClass\n      name as sanitized String?","explanation":"The sanitized modifier can only appear on incoming function or method parameters. Fields cannot be sanitized because sanitization is a boundary concern. See ek9 -h E07920 for details."}],"companions":[]}
{"id":848,"category":"Functions and Methods","question":"Can I assign an abstract function as a delegate in EK9?","url":"https://ek9.io/qa/QA0848.html","alternatePhrasings":["What is E50070 in EK9?","Why can I not use an abstract function as a delegate?","How do I assign a function reference in EK9?","What is the correct way to delegate to a function?"],"answer":"You cannot assign an abstract function reference as a delegate. Abstract functions have no implementation body, so there is nothing to execute. You must assign a concrete function that provides an actual implementation.\n\nWHY ABSTRACT FUNCTIONS CANNOT BE DELEGATES\nA delegate is a callable reference. Abstract functions declare a signature but have no body. Assigning one would create a delegate that cannot be called, which is a compile-time error rather than a runtime failure.\n\nCORRECT PATTERN\nDefine an abstract function, implement it with a concrete subtype, and assign the concrete version:\n  Transformer as abstract\n    -> input as String\n    <- rtn as String?\n  UpperTransformer() is Transformer\n    -> input as String\n    <- rtn as String: input.upperCase()\n  handler as Transformer: UpperTransformer\n\nINCORRECT PATTERN\n  handler as Transformer: Transformer  // E50070: abstract, no body\n\nSee Q100 for function basics. See Q102 for function composition. See Q106 for higher-order functions.","ek9Example":"defines module qa.functions.abstract.assignment\n\n  defines function\n\n    <?-\n      Abstract function declaring the contract.\n      Cannot be used directly as a delegate.\n    -?>\n    Transformer as abstract\n      -> input as String\n      <- rtn as String?\n\n    <?-\n      Concrete implementation of Transformer.\n      This can be assigned as a delegate.\n    -?>\n    UpperTransformer() is Transformer\n      -> input as String\n      <- rtn as String: input.upperCase()\n\n  defines program\n\n    AbstractFunctionDemo()\n      stdout <- Stdout()\n\n      //Correct: assign concrete function\n      handler as Transformer: UpperTransformer\n      if handler?\n        stdout.println(handler(\"hello\"))","migrationContext":"Java: abstract classes cannot be instantiated but interfaces can be lambdas. Python: ABCMeta prevents instantiation at runtime. Kotlin: abstract functions cannot be referenced directly. Rust: trait objects require concrete implementations. EK9: E50070 prevents assigning abstract functions as delegates at compile time.","keywords":["E50070","abstract","assignment","callable","concrete","delegate","function","implementation","reference"],"primaryTopics":[],"typicalErrors":[{"error":"E50070","correct":"    handler as Transformer: UpperTransformer","incorrect":"    handler as Transformer: Transformer","explanation":"Abstract functions have no body and cannot be assigned as delegates. Use a concrete implementation instead. See ek9 -h E50070 for details."}],"companions":[]}
{"id":849,"category":"Dispatcher Validation","question":"Why can't a dispatcher see a private handler method in a superclass?","url":"https://ek9.io/qa/QA0849.html","alternatePhrasings":["What triggers E05180 DISPATCHER_PRIVATE_IN_SUPER?","Why must dispatcher handler methods be protected or public?","How do I fix private dispatcher handler visibility?"],"answer":"When a subclass has a dispatcher, it searches the superclass for matching handler methods. Private methods in the superclass are invisible. Use protected instead.\n\nSee Q616 for dispatcher ambiguity. See Q621 for dispatch rules.","ek9Example":"defines module qa.dispatcher.private.super\n\n  defines class\n\n    BaseHandler as open\n\n      protected process()\n        -> arg0 as Integer\n        require arg0?\n\n      default operator ?\n\n    SubDispatcher extends BaseHandler\n\n      process() as dispatcher\n        -> arg0 as Any\n        require arg0?\n\n      runDemo()\n        this.process(42)\n\n      default operator ?\n\n  defines function\n\n    DispatcherDemo()\n      handler <- SubDispatcher()\n      handler.runDemo()\n      require handler?","migrationContext":"Java: Visitor pattern requires public. C#: dynamic dispatch ignores private. EK9: compile-time error if dispatcher handler is private in super.","keywords":["E05180","dispatcher","handler","private","protected","super"],"primaryTopics":["dispatcher visibility","E05180"],"typicalErrors":[{"error":"E05180","correct":"      protected process()\n        -> arg0 as Integer\n        require arg0?","incorrect":"      private process()\n        -> arg0 as Integer\n        require arg0?","explanation":"Private methods in a superclass are invisible to subclass dispatchers. Use protected. See ek9 -h E05180 for details."}],"companions":[]}
{"id":850,"category":"Sealed Types and Traits","question":"Can an abstract class appear in a trait's allow only list?","url":"https://ek9.io/qa/QA0850.html","alternatePhrasings":["What is E05250 in EK9?","Why must allow only list concrete classes?","What types can appear in allow only?","Can I seal a trait to abstract classes?"],"answer":"A trait's 'allow only' list must contain only concrete classes. Abstract classes cannot be instantiated, so listing them in 'allow only' is meaningless and raises E05250.\n\nWHY ONLY CONCRETE CLASSES\nThe 'allow only' mechanism restricts which classes can implement a trait. Since abstract classes cannot be instantiated directly, allowing them would create a gap: any concrete subclass of that abstract class could implement the trait without being explicitly listed. This defeats the purpose of sealing.\n\nCORRECT PATTERN\nList only concrete, instantiable classes:\n  Workable allow only FastWorker, SlowWorker\n    execute() as abstract\n      <- rtn as String?\n\nINCORRECT PATTERN\n  Workable allow only AbstractHelper, ConcreteWorker  // E05250\n\nSee Q130 for trait basics. See Q132 for sealed types. See Q134 for allow only patterns.","ek9Example":"defines module qa.sealed.allow.only.concrete\n\n  defines class\n\n    <?-\n      Abstract helper class — cannot appear in allow only lists.\n    -?>\n    AbstractHelper as abstract\n      execute() as abstract\n        <- rtn as String?\n      default operator ?\n\n    <?-\n      First concrete worker class.\n      Can appear in allow only lists because it is concrete.\n    -?>\n    FastWorker\n      execute()\n        <- rtn as String: \"fast\"\n      default operator ?\n\n    <?-\n      Second concrete worker class.\n      Also eligible for allow only lists.\n    -?>\n    SlowWorker\n      execute()\n        <- rtn as String: \"slow\"\n      default operator ?\n\n  defines trait\n\n    <?-\n      Trait sealed to only FastWorker and SlowWorker.\n      No other class can implement this trait.\n    -?>\n    Workable allow only FastWorker, SlowWorker\n      execute() as abstract\n        <- rtn as String?\n\n  defines program\n\n    AllowOnlyDemo()\n      stdout <- Stdout()\n\n      fast <- FastWorker()\n      if fast?\n        stdout.println(fast.execute())","migrationContext":"Java: sealed interfaces permit abstract classes (different design). Kotlin: sealed interfaces allow abstract implementations. Rust: no sealed traits (crate visibility instead). Scala: sealed traits allow abstract subtypes. EK9: E05250 requires all allow only entries to be concrete classes.","keywords":["E05250","abstract","allow","concrete","implement","instantiate","only","restrict","sealed","trait"],"primaryTopics":[],"typicalErrors":[{"error":"E05250","correct":"    Workable allow only FastWorker, SlowWorker","incorrect":"    Workable allow only AbstractHelper, SlowWorker","explanation":"Abstract classes cannot appear in allow only lists because they cannot be instantiated. Only list concrete classes that will implement the trait. See ek9 -h E05250 for details."}],"companions":[]}
{"id":851,"category":"Syntax and Structure Rules","question":"Why do references need a module qualifier with :: in EK9?","url":"https://ek9.io/qa/QA0851.html","alternatePhrasings":["What is E01010 in EK9?","How do I reference types from other modules?","What is the correct reference syntax in EK9?","Why does a bare type name in references cause an error?"],"answer":"References in EK9 must include the module qualifier with the '::' separator. A bare type name without a module path is invalid and raises E01010.\n\nWHY MODULE QUALIFIERS ARE REQUIRED\nEK9 uses explicit module references to avoid ambiguity. Two modules might define a type with the same name. The module qualifier ensures the compiler resolves the correct type. Without it, the reference is ambiguous and meaningless.\n\nCORRECT SYNTAX\n  references\n    some.module::SomeType\n    another.module::AnotherType\n\nINCORRECT SYNTAX\n  references\n    SomeType           // E01010: no module qualifier\n    ::SomeType         // E01010: missing module name\n\nSee Q10 for module structure. See Q12 for module naming. See Q852 for reserved module names.","ek9Example":"defines module qa.syntax.reference.qualifier\n\n  defines function\n\n    <?-\n      Simple function demonstrating correct module code.\n      References use the format: module.name::SymbolName\n    -?>\n    formatGreeting() as pure\n      -> name as String\n      <- rtn as String: `Hello, ${name}!`\n\n  defines program\n\n    ReferenceDemo()\n      stdout <- Stdout()\n\n      //Correct references use: module.name::SymbolName\n      //Bare names like 'SomeType' without module:: cause E01010\n      stdout.println(formatGreeting(\"Steve\"))","migrationContext":"Java: import statements use dot notation (java.util.List). Python: import uses dot notation (from os.path import join). Rust: use statements with :: (std::collections::HashMap). Go: import with path strings. EK9: references use module.name::SymbolName with :: separator, E01010 if module qualifier missing.","keywords":["E01010","import","module","namespace","qualifier","reference","separator","structure","symbol","syntax"],"primaryTopics":[],"typicalErrors":[{"error":"E01010","correct":"defines module qa.syntax.reference.qualifier","incorrect":"defines module qa.syntax.reference.qualifier\n\n  references\n    SomeType","explanation":"References must include the full module path followed by :: and the symbol name. Bare type names without a module qualifier are invalid. See ek9 -h E01010 for details."}],"companions":[]}
{"id":852,"category":"Syntax and Structure Rules","question":"Which module names are reserved in EK9?","url":"https://ek9.io/qa/QA0852.html","alternatePhrasings":["What is E01020 in EK9?","Can I use org.ek9.lang as my module name?","What namespaces are reserved for built-in types?","Why does my module name cause a compiler error?"],"answer":"The org.ek9.lang and org.ek9.math namespaces are reserved for EK9 built-in types. User code must use different module names. Attempting to define a module in these namespaces raises E01020.\n\nWHY RESERVED\nBuilt-in types like String, Integer, Boolean, List, and Dict live in org.ek9.lang. Mathematical types and functions live in org.ek9.math. Allowing user code in these namespaces would create conflicts with built-in type definitions and break the bootstrap process.\n\nRESERVED NAMESPACES\n- org.ek9.lang (core types: String, Integer, Boolean, List, Dict, etc.)\n- org.ek9.math (mathematical types and functions)\n\nCORRECT MODULE NAMES\n- com.mycompany.myproject\n- qa.syntax.example\n- app.services.auth\n\nINCORRECT MODULE NAMES\n- org.ek9.lang (E01020)\n- org.ek9.lang.custom (E01020)\n- org.ek9.math.extra (E01020)\n\nSee Q10 for module structure. See Q851 for reference syntax. See Q14 for package organization.","ek9Example":"defines module qa.syntax.reserved.modules\n\n  defines function\n\n    <?-\n      Simple function in a user namespace.\n      Reserved namespaces org.ek9.lang and org.ek9.math cannot be used.\n    -?>\n    getVersion() as pure\n      <- rtn <- \"1.0.0\"\n\n  defines program\n\n    ReservedModuleDemo()\n      stdout <- Stdout()\n\n      //Reserved namespaces: org.ek9.lang, org.ek9.math\n      //User code must choose different module names\n      stdout.println(`Version: ${getVersion()}`)","migrationContext":"Java: java.lang is reserved by convention (JLS prohibits it). Python: no reserved package names (but shadowing stdlib is a common bug). Rust: std is reserved. Go: no reserved paths (but shadowing stdlib is discouraged). EK9: org.ek9.lang and org.ek9.math are reserved at compile time, E01020 enforced.","keywords":["E01020","bootstrap","built-in","conflict","module","namespace","org.ek9.lang","org.ek9.math","reserved","syntax"],"primaryTopics":[],"typicalErrors":[{"error":"E01020","correct":"defines module qa.syntax.reserved.modules","incorrect":"defines module org.ek9.lang","explanation":"The org.ek9.lang namespace is reserved for EK9 built-in types. Choose a different module name for your code. See ek9 -h E01020 for details."}],"companions":[]}
{"id":853,"category":"Code Quality","question":"What happens when parent and child classes have duplicate field names with JSON operator?","url":"https://ek9.io/qa/QA0853.html","alternatePhrasings":["What is E02020 in EK9?","Why does EK9 flag duplicate property names in JSON?","Can parent and child have the same field name with operator $$?","How does EK9 prevent ambiguous JSON output?"],"answer":"When a class hierarchy uses 'default operator $$' (JSON serialization), property names must be unique across the entire hierarchy. If a parent and child class both have a field with the same name, the JSON output would be ambiguous, raising E02020.\n\nWHY UNIQUE NAMES ARE REQUIRED\nJSON keys must be unique within an object. If a parent has field 'label' and a child also has field 'label', the JSON serializer cannot produce valid output. Rather than silently picking one or producing invalid JSON, EK9 catches this at compile time.\n\nCORRECT PATTERN\nUse unique field names across the hierarchy:\n  BaseConfig as open\n    configName <- \"default\"\n  ExtendedConfig extends BaseConfig\n    priority <- 0\n\nINCORRECT PATTERN\n  BaseConfig as open\n    label <- \"default\"\n  ExtendedConfig extends BaseConfig\n    label <- \"custom\"     // E02020: duplicate property\n\nSee Q200 for JSON basics. See Q202 for JSON operator patterns. See Q315 for inheritance depth.","ek9Example":"defines module qa.quality.duplicate.json.property\n\n  defines class\n\n    <?-\n      Base configuration class with configName field.\n      Uses default operator for JSON serialization.\n    -?>\n    BaseConfig as open\n      configName <- \"default\"\n\n      BaseConfig()\n        -> cn as String\n        configName :=: cn\n\n      operator <=> as pure\n        -> arg0 as BaseConfig\n        <- rtn as Integer: configName <=> arg0.configName\n\n      default operator\n\n    <?-\n      Extended configuration with unique field name 'priority'.\n      Does NOT reuse 'configName' which would cause E02020.\n    -?>\n    ExtendedConfig extends BaseConfig\n      priority <- 0\n\n      ExtendedConfig()\n        ->\n          cn as String\n          p as Integer\n        super(cn)\n        priority :=: p\n\n      override operator <=> as pure\n        -> arg0 as ExtendedConfig\n        <- rtn as Integer: priority <=> arg0.priority\n\n      default operator\n\n  defines program\n\n    JsonPropertyDemo()\n      stdout <- Stdout()\n\n      config <- ExtendedConfig(\"prod\", 10)\n      if config?\n        stdout.println(\"Config created: \" + $config)","migrationContext":"Java: Jackson allows field shadowing (confusing JSON). Python: no compile-time JSON validation. Kotlin: Kotlinx serialization detects some conflicts. Rust: Serde handles field shadowing but can be confusing. Go: encoding/json uses last field wins. EK9: E02020 prevents duplicate JSON property names at compile time.","keywords":["E02020","JSON","ambiguous","compile-time","duplicate","field","hierarchy","operator","property","serialization"],"primaryTopics":[],"typicalErrors":[{"error":"E02020","correct":"    ExtendedConfig extends BaseConfig\n      priority <- 0","incorrect":"    ExtendedConfig extends BaseConfig\n      configName <- \"custom\"","explanation":"Parent BaseConfig already has a field named configName. The child cannot reuse that name when both use default operator $$ for JSON. Use a unique field name in the child. See ek9 -h E02020 for details."}],"companions":[]}
{"id":854,"category":"Generics","question":"Why can't I use type inference inside a generic class body in EK9?","url":"https://ek9.io/qa/QA0854.html","alternatePhrasings":["What triggers E06070 TYPE_INFERENCE_NOT_SUPPORTED?","Why does 'inferred <- 2' fail inside a generic class?","Why must all types be explicit in generic bodies?"],"answer":"Type inference with '<-' is not allowed inside generic/template class bodies. All types must be explicit because the type parameter T is unknown until instantiation.\n\nSee Q646 for type inference limits. See Q58 for generic functions.","ek9Example":"defines module qa.generics.no.inference\n\n  defines class\n\n    Container of type T\n\n      default Container()\n\n      default Container()\n        -> item as T\n\n      someMethod()\n        someVar as Integer: 2\n        require someVar?\n\n  defines program\n\n    GenericInferenceDemo()\n      stdout <- Stdout()\n      greeting <- \"hello\"\n      stdout.println(greeting)","migrationContext":"Java: allows var inside generic bodies. Kotlin: allows inference everywhere. Rust: allows inference in generic impls. EK9: forbids all inference in generic bodies for consistency.","keywords":["E06070","explicit","generic","inference","template","type"],"primaryTopics":["generic type inference","E06070"],"typicalErrors":[{"error":"E06070","correct":"        someVar as Integer: 2","incorrect":"        someVar <- 2","explanation":"Type inference is not supported inside generic class bodies. Declare all types explicitly. See ek9 -h E06070 for details."}],"companions":[]}
{"id":855,"category":"Code Quality","question":"Why does EK9 reject repeated parameter groups across functions?","url":"https://ek9.io/qa/QA0855.html","alternatePhrasings":["What triggers E11053 DATA_CLUMP_DETECTED?","Why should I extract repeated parameters into a record?","What is a data clump in EK9?"],"answer":"When 3+ functions share 4+ parameters with matching names and types, EK9 detects a data clump (E11053). Extract the parameters into a record.\n\nSee Q97 for class vs record. See Q311 for quality checks.","ek9Example":"defines module qa.quality.data.clump.record\n\n  defines record\n\n    Address\n      street <- String()\n      city <- String()\n      state <- String()\n      zipCode <- String()\n\n      Address()\n        ->\n          s as String\n          c as String\n          st as String\n          z as String\n        street :=: s\n        city :=: c\n        state :=: st\n        zipCode :=: z\n\n      operator <=> as pure\n        -> arg0 as Address\n        <- rtn as Integer: street <=> arg0.street\n\n      default operator\n\n  defines function\n\n    formatAddress() as pure\n      -> addr as Address\n      <- rtn as String: `${addr.street}, ${addr.city}`\n\n    validateAddress() as pure\n      -> addr as Address\n      <- rtn as Boolean: addr.street? and addr.city?\n\n    normalizeAddress()\n      -> addr as Address\n      <- rtn as Address: Address(s: addr.street, c: addr.city, st: addr.state.upperCase(), z: addr.zipCode)\n\n  defines program\n\n    DataClumpDemo()\n      stdout <- Stdout()\n      addr <- Address(s: \"123 Main\", c: \"Springfield\", st: \"IL\", z: \"62704\")\n      if validateAddress(addr)\n        stdout.println(formatAddress(addr))","migrationContext":"Java: no detection, relies on SonarQube. Python: no type-aware detection. Go: favors structs but not enforced. EK9: compile-time error requiring record extraction.","keywords":["E11053","clump","data","extract","parameter","quality","record"],"primaryTopics":["data clump","E11053","parameter grouping"],"typicalErrors":[],"companions":[]}
{"id":856,"category":"Code Quality","question":"When should I use a component instead of a class in EK9?","url":"https://ek9.io/qa/QA0856.html","alternatePhrasings":["What triggers E11022 CLASS_SHOULD_BE_COMPONENT?","Why does EK9 reject my class with service fields?","What is the difference between class and component in EK9?"],"answer":"A class with 4+ service fields (traits/abstract types) and 0 data fields is architecturally a service coordinator — it should be a component, not a class.\n\nCOMPONENTS vs CLASSES\n- Component: singleton lifecycle, service coordination, DI\n- Class: multiple instances, data + behavior\n\nTHIS EXAMPLE\nServiceCoordinator uses 4 trait dependencies. It is correctly defined as a component. If it were a class, E11022 would fire.\n\nSee Q111 for components. See Q227 for DI.","ek9Example":"defines module qa.quality.class.vs.component\n\n  defines trait\n    ServiceA\n      doA() abstract\n    ServiceB\n      doB() abstract\n    ServiceC\n      doC() abstract\n    ServiceD\n      doD() abstract\n\n  defines class\n    ImplA with trait of ServiceA\n      override doA()\n        require true\n    ImplB with trait of ServiceB\n      override doB()\n        require true\n    ImplC with trait of ServiceC\n      override doC()\n        require true\n    ImplD with trait of ServiceD\n      override doD()\n        require true\n\n  defines component\n\n    ServiceCoordinator\n      svcA ServiceA: ImplA()\n      svcB ServiceB: ImplB()\n      svcC ServiceC: ImplC()\n      svcD ServiceD: ImplD()\n\n      orchestrate()\n        svcA.doA()\n        svcB.doB()\n        svcC.doC()\n        svcD.doD()\n\n      default operator ?\n\n  defines program\n\n    ComponentDemo()\n      stdout <- Stdout()\n      stdout.println(\"Component handles service coordination\")","migrationContext":"Java: no class/component distinction — Spring @Component is optional. C#: no language-level enforcement. Go: no component construct. EK9: compiler enforces class vs component based on field analysis.","keywords":["E11022","class","component","design","field","quality","service","trait"],"primaryTopics":["class vs component","E11022"],"typicalErrors":[{"error":"E11022","correct":"  defines component","incorrect":"  defines class","explanation":"A class with only service/trait fields and no data fields is a service coordinator. Use 'defines component' instead. See ek9 -h E11022 for details."}],"companions":[]}
{"id":857,"category":"Streams and Pipelines","question":"Why does stream split require a function or delegate?","url":"https://ek9.io/qa/QA0857.html","alternatePhrasings":["What triggers E07860 FUNCTION_OR_DELEGATE_REQUIRED?","Why can't I use split without a predicate in EK9 streams?","What does stream split need as an argument?"],"answer":"Stream split requires a predicate function that takes one argument and returns Boolean. Without a predicate, the stream cannot decide how to split elements.\n\nSee Q813 for stream rules.","ek9Example":"defines module qa.streams.split.needs.function\n\n  defines constant\n\n    SHORT_THRESHOLD <- 2\n\n  defines function\n\n    isShort() as pure\n      -> item as String\n      <- rtn as Boolean: length item < SHORT_THRESHOLD\n\n  defines program\n\n    StreamSplitDemo()\n      collection <- List() of List of String\n      cat [\"A\", \"B\", \"C\"] | split by isShort > collection\n      require collection?","migrationContext":"Java: no built-in split stream op. Python: itertools has no split. EK9: split requires predicate function.","keywords":["E07860","delegate","function","pipeline","predicate","split","stream"],"primaryTopics":["stream split predicate","E07860","FUNCTION_OR_DELEGATE_REQUIRED"],"typicalErrors":[{"error":"E07860","correct":"      cat [\"A\", \"B\", \"C\"] | split by isShort > collection","incorrect":"      cat [\"A\", \"B\", \"C\"] | split > collection","explanation":"Stream split requires a predicate function. Without one, the stream cannot decide how to split. See ek9 -h E07860 for details."}],"companions":[]}
{"id":858,"category":"Dispatcher Validation","question":"How do I avoid dispatcher handler ambiguity with multiple traits?","url":"https://ek9.io/qa/QA0858.html","alternatePhrasings":["What triggers E05230 DISPATCHER_HANDLER_AMBIGUITY?","Why is Duck ambiguous when it implements two traits?","How do I resolve dispatcher ambiguity in EK9?"],"answer":"When a type implements two traits and handlers exist for both traits at equal cost, the dispatcher cannot choose. Add a specific handler for the ambiguous type.\n\nTHIS EXAMPLE\nDuck implements Flyable. The processor has a handler for Dog (concrete). Replacing the Dog handler with a Flyable handler would create ambiguity for Duck if Swimmable were also handled.\n\nSee Q616 for dispatcher ambiguity. See Q621 for dispatch rules.","ek9Example":"defines module qa.dispatcher.no.ambiguity\n\n  defines trait\n\n    Flyable\n      fly()\n        Stdout().println(\"Flying\")\n\n    Swimmable\n      swim()\n        Stdout().println(\"Swimming\")\n\n  defines class\n\n    Animal as abstract\n      speak() as abstract\n      default operator ?\n\n    Dog is Animal\n      override speak()\n        Stdout().println(\"Woof\")\n\n    Duck is Animal with trait of Flyable, Swimmable\n      override speak()\n        Stdout().println(\"Quack\")\n      override operator ? as pure\n        <- rtn as Boolean: true\n\n    AnimalProcessor\n\n      process() as dispatcher\n        -> animal as Animal\n        animal.speak()\n\n      process()\n        -> animal as Dog\n        animal.speak()\n\n      default operator ?\n\n  defines program\n\n    DispatcherDemo()\n      processor <- AnimalProcessor()\n      dog as Animal: Dog()\n      if processor? and dog?\n        processor.process(dog)","migrationContext":"Java: method overloading is compile-time. C#: dynamic dispatch at runtime. Kotlin: sealed when is exhaustive. EK9: compile-time ambiguity detection for all reachable runtime types.","keywords":["E05230","ambiguity","dispatcher","handler","multiple","trait"],"primaryTopics":["dispatcher ambiguity","E05230","multiple traits"],"typicalErrors":[],"companions":[]}
{"id":859,"category":"Code Quality","question":"When should I use 'by' delegation instead of manual method forwarding?","url":"https://ek9.io/qa/QA0859.html","alternatePhrasings":["What is E11023 MISSING_BY_DELEGATION in EK9?","Why does EK9 require 'by' delegation for trait forwarding?","How do I eliminate boilerplate delegation code?"],"answer":"When a class manually delegates most trait methods to a field, EK9 detects this pattern and requires using the 'by' keyword instead (E11023). The 'by' keyword eliminates boilerplate delegation code.\n\nTHE PROBLEM\nManually overriding 5+ trait methods just to forward calls to a field is tedious and error-prone:\n  override methodA() -> delegate.methodA()\n  override methodB() -> delegate.methodB()\n  override methodC() -> delegate.methodC()\nThis is boilerplate that the compiler can generate automatically.\n\nTHE FIX\nUse 'with trait of X by field' to auto-delegate all unoverridden methods:\n  MyClass with trait of Formatter by delegate\n    delegate as Formatter: ConcreteFormatter()\nNow only override the methods you need to customize.\n\nBENEFITS\n1. Eliminates N lines of forwarding boilerplate\n2. New trait methods are automatically delegated\n3. Only customized methods need override\n4. Clear intent: delegation is visible in the class declaration\n\nSee Q210 for trait delegation basics. See Q264 for adapter pattern. See Q266 for cross-cutting concerns via delegation.","ek9Example":"defines module qa.quality.use.by.delegation\n\n  defines trait\n\n    <?-\n      Formatter trait with multiple methods.\n      Used to demonstrate delegation with 'by'.\n    -?>\n    Formatter\n      formatHeading() as abstract\n        -> heading as String\n        <- rtn as String?\n\n      formatParagraph() as abstract\n        -> paragraph as String\n        <- rtn as String?\n\n      formatFooter() as abstract\n        <- rtn as String?\n\n  defines class\n\n    <?-\n      Concrete implementation of Formatter.\n      Provides default formatting behavior.\n    -?>\n    PlainFormatter with trait of Formatter\n      override formatHeading()\n        -> heading as String\n        <- rtn as String: `=== ${heading} ===`\n\n      override formatParagraph()\n        -> paragraph as String\n        <- rtn as String: \"  \" + paragraph\n\n      override formatFooter()\n        <- rtn as String: \"---end---\"\n\n      default operator ?\n\n    <?-\n      Correct: uses 'by' delegation to auto-delegate Formatter methods.\n      Only overrides formatHeading to add custom behavior.\n      formatParagraph and formatFooter are auto-delegated.\n    -?>\n    FancyFormatter with trait of Formatter by delegate\n      delegate as Formatter: PlainFormatter()\n\n      FancyFormatter()\n        -> formatter as Formatter\n        this.delegate: formatter\n\n      override formatHeading()\n        -> heading as String\n        <- rtn as String: `*** ${heading} ***`\n\n      default operator ?\n\n  defines program\n\n    DelegationDemo()\n      stdout <- Stdout()\n\n      plain <- PlainFormatter()\n      fancy <- FancyFormatter(plain)\n\n      //formatHeading is overridden in FancyFormatter\n      stdout.println(fancy.formatHeading(\"Title\"))\n\n      //formatParagraph is auto-delegated to PlainFormatter\n      stdout.println(fancy.formatParagraph(\"Some content here\"))\n\n      //formatFooter is auto-delegated to PlainFormatter\n      stdout.println(fancy.formatFooter())","migrationContext":"Java: no built-in delegation, manual forwarding or Lombok @Delegate. Kotlin: 'by' delegation on interfaces, same concept. Rust: no delegation, manual impl forwarding. Go: embedded structs provide implicit forwarding. Python: __getattr__ for dynamic delegation. EK9: 'with trait of X by field' with E11023 enforcement when manual delegation detected.","keywords":["E11023","boilerplate","by","clean-code","composition","delegation","forwarding","override","quality","trait"],"primaryTopics":["by delegation requirement","E11023","MISSING_BY_DELEGATION"],"typicalErrors":[],"companions":[]}
{"id":860,"category":"Code Quality","question":"Why can't I mix 'by' delegation with many service fields?","url":"https://ek9.io/qa/QA0860.html","alternatePhrasings":["What is E11024 HYBRID_CLASS_COMPONENT in EK9?","Why does EK9 flag my class as a hybrid class-component?","How do I separate delegation from service coordination?"],"answer":"Using 'by' delegation (class pattern) while having 3+ service fields (component pattern) mixes two architectural roles in one type. The compiler raises E11024 to enforce separation of concerns.\n\nWHY THIS IS A PROBLEM\nDelegation classes wrap and decorate a single concern. Components coordinate multiple services. Combining both patterns creates a type that is half-decorator and half-coordinator, making it hard to test, reason about, and maintain.\n\nTHE FIX\nSplit into two types:\n1. A focused delegation class that wraps one trait with 'by'\n2. A component that coordinates multiple services\n\nTHIS EXAMPLE\nShows the correct approach: a delegation class (LoggingReporter) wraps a single trait with 'by' and has at most one or two service fields. The service coordination is kept separate.\n\nSee Q856 for class vs component. See Q859 for 'by' delegation. See Q310 for code quality overview.","ek9Example":"defines module qa.quality.no.hybrid.class.component\n\n  defines trait\n\n    <?-\n      Reporter trait: a single concern for reporting.\n    -?>\n    Reporter\n      report() as abstract\n        -> topic as String\n        <- rtn as String?\n\n  defines class\n\n    <?-\n      Concrete reporter implementation.\n    -?>\n    PlainReporter with trait of Reporter\n      override report()\n        -> topic as String\n        <- rtn as String: \"Report on \" + topic\n\n      default operator ?\n\n    <?-\n      Correct: focused delegation class.\n      Wraps Reporter with 'by' and has minimal service fields.\n      No hybrid mixing of delegation and service coordination.\n    -?>\n    LoggingReporter with trait of Reporter by delegate\n      delegate as Reporter: PlainReporter()\n\n      LoggingReporter()\n        -> reporter as Reporter\n        this.delegate: reporter\n\n      override report()\n        -> topic as String\n        <- rtn as String: \"[LOG] \" + delegate.report(topic)\n\n      default operator ?\n\n  defines program\n\n    NoHybridDemo()\n      stdout <- Stdout()\n\n      plain <- PlainReporter()\n      logging <- LoggingReporter(plain)\n\n      //Delegation class focuses on one concern: adding logging\n      stdout.println(logging.report(\"quarterly-sales\"))\n      stdout.println(logging.report(\"annual-review\"))","migrationContext":"Java: no detection of mixed delegation and service patterns. Spring allows mixing @Delegate with @Autowired freely. Python: no detection. Kotlin: 'by' delegation has no service field checks. Rust: no class/component distinction. EK9: E11024 compile-time enforcement separating delegation from service coordination.","keywords":["E11024","architecture","by","class","clean-code","component","delegation","hybrid","quality","separation","service"],"primaryTopics":["hybrid class-component detection","E11024","HYBRID_CLASS_COMPONENT"],"typicalErrors":[{"error":"E11024","correct":"      delegate as Reporter: PlainReporter()\n\n      LoggingReporter()","incorrect":"      delegate as Reporter: PlainReporter()\n      svcA as Reporter: PlainReporter()\n      svcB as Reporter: PlainReporter()\n      svcC as Reporter: PlainReporter()\n      auditLabel as String: \"audit\"\n\n      LoggingReporter()","explanation":"Combining 'by' delegation with three or more additional service fields mixes decorator and coordinator roles and triggers E11024 - split into a focused delegation class and a separate component. See ek9 -h E11024 for details."}],"companions":[]}
{"id":861,"category":"Code Quality","question":"Why does EK9 reject classes with too many service and data fields?","url":"https://ek9.io/qa/QA0861.html","alternatePhrasings":["What triggers E11025 EXCESSIVE_MIXED_RESPONSIBILITIES?","What is the God Class anti-pattern in EK9?","How many service fields can a class have alongside data fields?"],"answer":"A class with 3+ service fields AND 3+ data fields is a 'God Class'. Split into a data class (few services) and a service component.\n\nTHRESHOLD: >= 3 service fields AND >= 3 data fields.\n\nTHIS EXAMPLE\nOrderHandler has 2 service fields + 3 data fields = OK (under threshold).\n\nSee Q856 for class vs component. See Q311 for quality checks.","ek9Example":"defines module qa.quality.avoid.god.class\n\n  defines trait\n    ValidatorService\n      validate() abstract\n    PricingService\n      calculatePrice() abstract\n    ShippingService\n      ship() abstract\n\n  defines class\n    ValidatorImpl with trait of ValidatorService\n      override validate()\n        require true\n    PricerImpl with trait of PricingService\n      override calculatePrice()\n        require true\n    ShipperImpl with trait of ShippingService\n      override ship()\n        require true\n\n  defines class\n\n    OrderHandler\n      //2 service fields (under threshold)\n      validator ValidatorService: ValidatorImpl()\n      pricer PricingService: PricerImpl()\n      //3 data fields\n      orderId <- \"ORD-001\"\n      customerName <- \"Steve\"\n      totalAmount <- 0\n\n      processOrder()\n        validator.validate()\n        pricer.calculatePrice()\n\n      default operator ?\n\n  defines program\n\n    GodClassDemo()\n      stdout <- Stdout()\n      handler <- OrderHandler()\n      if handler?\n        handler.processOrder()\n        stdout.println(\"Order processed\")","migrationContext":"Java: detected only by SonarQube. Python: no detection. Go: convention only. EK9: compile-time error preventing God Class.","keywords":["E11025","class","field","god","mixed","quality","responsibilities","service"],"primaryTopics":["God Class","E11025","mixed responsibilities"],"typicalErrors":[{"error":"E11025","correct":"      //2 service fields (under threshold)\n      validator ValidatorService: ValidatorImpl()\n      pricer PricingService: PricerImpl()","incorrect":"      //3 service fields (triggers threshold)\n      validator ValidatorService: ValidatorImpl()\n      pricer PricingService: PricerImpl()\n      shipper ShippingService: ShipperImpl()","explanation":"3+ service fields AND 3+ data fields triggers E11025. Split into a data class and a service component. See ek9 -h E11025 for details."}],"companions":[]}
{"id":862,"category":"Dependency Injection","question":"Why does EK9 limit injection fields per component?","url":"https://ek9.io/qa/QA0862.html","alternatePhrasings":["What is E11040 EXCESSIVE_INJECTION_FIELDS in EK9?","How many injection fields can a component have?","How do I fix too many injected dependencies?"],"answer":"Components with more than 4 injection fields (marked with '!') violate the Single Responsibility Principle. The compiler raises E11040 to enforce focused components.\n\nWHY THE LIMIT EXISTS\nA component with 5+ injected dependencies is doing too much. Each dependency represents a concern the component must manage. More than 4 suggests the component should be split.\n\nTHE FIX\nExtract a facade component to group related dependencies:\n1. Identify clusters of related injected services\n2. Create a facade component that wraps the cluster\n3. Inject the facade instead of individual services\n\nTHIS EXAMPLE\nShows a component with exactly 3 injection fields, well within the limit. Each dependency is clearly needed for the component's focused responsibility.\n\nSee Q227 for compile-time DI validation. See Q229 for circular deps. See Q325 for component patterns.","ek9Example":"defines module qa.di.injection.field.limit\n\n  defines component\n\n    <?-\n      Abstract service contracts for injection.\n    -?>\n    Repository as abstract\n      findById() as abstract\n        -> identifier as String\n        <- rtn as String?\n      default operator ?\n\n    Notifier as abstract\n      notify() as abstract\n        -> message as String\n      default operator ?\n\n    Auditor as abstract\n      record() as abstract\n        -> action as String\n      default operator ?\n\n    <?-\n      Concrete implementations.\n    -?>\n    InMemoryRepository extends Repository\n      override findById()\n        -> identifier as String\n        <- rtn as String: \"item-\" + identifier\n      default operator ?\n\n    ConsoleNotifier extends Notifier\n      override notify()\n        -> message as String\n        Stdout().println(\"[NOTIFY] \" + message)\n      default operator ?\n\n    ConsoleAuditor extends Auditor\n      override record()\n        -> action as String\n        Stdout().println(\"[AUDIT] \" + action)\n      default operator ?\n\n    <?-\n      Correct: 3 injection fields, within the limit of 4.\n      Each dependency is clearly needed for order processing.\n    -?>\n    OrderProcessor\n\n      repo as Repository!\n      notifier as Notifier!\n      auditor as Auditor!\n\n      processOrder()\n        -> orderId as String\n        item <- repo.findById(orderId)\n        if item?\n          notifier.notify(`Processing ${item}`)\n          auditor.record(`Processed order ${orderId}`)\n\n      default operator ?\n\n  defines application\n\n    OrderApp\n      register InMemoryRepository() as Repository\n      register ConsoleNotifier() as Notifier\n      register ConsoleAuditor() as Auditor\n\n  defines program\n\n    InjectionLimitDemo()\n      stdout <- Stdout()\n      stdout.println(\"OrderProcessor has 3 injection fields (within limit of 4)\")\n      stdout.println(\"Components with 5+ injection fields trigger E11040\")","migrationContext":"Java: Spring has no limit on @Autowired fields (only advisory from SonarQube). Python: no limit. Kotlin: no limit. Go: no DI framework limits. Rust: no DI concept. EK9: E11040 compile-time limit of 4 injection fields per component.","keywords":["E11040","SRP","clean-code","component","dependency","facade","field","inject","injection","limit","quality"],"primaryTopics":["injection field limit","E11040","EXCESSIVE_INJECTION_FIELDS"],"typicalErrors":[{"error":"E11040","correct":"      repo as Repository!\n      notifier as Notifier!\n      auditor as Auditor!","incorrect":"      repo as Repository!\n      notifier as Notifier!\n      auditor as Auditor!\n      extra1 as Repository!\n      extra2 as Notifier!","explanation":"5 injection fields exceeds the limit of 4. Extract related services into a facade component and inject the facade instead. See ek9 -h E11040 for details."}],"companions":[]}
{"id":863,"category":"Code Quality","question":"Why must module constants be in a single file?","url":"https://ek9.io/qa/QA0863.html","alternatePhrasings":["What is E11066 CONSTANTS_IN_MULTIPLE_FILES in EK9?","How do I consolidate scattered constants?","Why does EK9 require constants in one file per module?"],"answer":"When constants for a module are scattered across 3+ source files, EK9 requires consolidation into a single file (E11066). This is a module-level check for discoverability.\n\nWHY CONSOLIDATION MATTERS\nConstants scattered across files are hard to find, easy to duplicate, and impossible to review as a group. Consolidating them into one file per module provides:\n1. A single reference point for all module constants\n2. Easy duplicate detection during code review\n3. Clear organization by concern using doc comments\n4. Simplified maintenance when values change\n\nTHE FIX\nMove all 'defines constant' blocks for a module into one file. Organize with doc comments:\n  defines constant\n    //Temperature thresholds\n    FREEZING_POINT <- 0.0\n    BOILING_POINT <- 100.0\n    //Retry configuration\n    MAX_RETRIES <- 3\n    RETRY_DELAY_MS <- 500\n\nTHIS EXAMPLE\nShows all module constants properly consolidated in a single file with clear organization.\n\nSee Q695 for named constants. See Q800 for magic literal detection. See Q310 for code quality overview.","ek9Example":"defines module qa.quality.consolidate.constants\n\n  defines constant\n\n    <?-\n      Temperature thresholds.\n      All temperature-related constants grouped together.\n    -?>\n    FREEZING_CELSIUS <- 0.0\n    BOILING_CELSIUS <- 100.0\n    BODY_TEMP_CELSIUS <- 37.0\n\n    <?-\n      Retry configuration constants.\n      Used by network and service components.\n    -?>\n    MAX_RETRIES <- 3\n    RETRY_DELAY_MS <- 500\n    CONNECTION_TIMEOUT_MS <- 5000\n\n    <?-\n      Formatting constants.\n      Used by output and report functions.\n    -?>\n    SEPARATOR_LINE <- \"----------------------------------------\"\n    INDENT_SPACES <- \"    \"\n\n  defines function\n\n    <?-\n      Classify temperature using consolidated constants.\n      All thresholds come from one constants block.\n    -?>\n    classifyTemperature() as pure\n      -> celsius as Float\n      <- classification as String: \"warm\"\n\n      if celsius <= FREEZING_CELSIUS\n        classification: \"freezing\"\n      else if celsius >= BOILING_CELSIUS\n        classification: \"boiling\"\n\n    <?-\n      Format a report line using the shared formatting constants.\n    -?>\n    formatReportLine() as pure\n      -> content as String\n      <- rtn as String: INDENT_SPACES + content\n\n  defines program\n\n    ConsolidatedConstantsDemo()\n      stdout <- Stdout()\n\n      stdout.println(SEPARATOR_LINE)\n      stdout.println(formatReportLine(\"Temperature Classifications\"))\n      stdout.println(SEPARATOR_LINE)\n\n      stdout.println(formatReportLine(`Ice water: ${classifyTemperature(-5.0)}`))\n      stdout.println(formatReportLine(`Room temp: ${classifyTemperature(22.0)}`))\n      stdout.println(formatReportLine(`Steam: ${classifyTemperature(105.0)}`))\n\n      stdout.println(SEPARATOR_LINE)\n      stdout.println(formatReportLine(`Max retries: ${MAX_RETRIES}`))\n      stdout.println(formatReportLine(`Timeout: ${CONNECTION_TIMEOUT_MS}ms`))","migrationContext":"Java: no enforcement of constant file organization. Python: convention to use constants.py but not enforced. Kotlin: no enforcement. Rust: no enforcement. Go: convention to use const blocks but scattered is allowed. EK9: E11066 compile-time enforcement requiring module constants in a single file.","keywords":["E11066","clean-code","consolidate","constant","discoverability","file","module","organization","quality","scattered"],"primaryTopics":["constant consolidation","E11066","CONSTANTS_IN_MULTIPLE_FILES"],"typicalErrors":[],"companions":[]}
{"id":864,"category":"Operators and Expressions","question":"Why must the negate operator ~ return the same type as its class?","url":"https://ek9.io/qa/QA0864.html","alternatePhrasings":["What triggers E07410 MUST_RETURN_SAME_AS_CONSTRUCT_TYPE?","Why does my negate operator fail with wrong return type?","How do I correctly implement the ~ negate operator in EK9?"],"answer":"The negate operator ~ must return the SAME type as the enclosing class. Returning a different type is a semantic error (E07410).\n\nWHAT NEGATE DOES\nNegate produces the logical or arithmetic inverse of the current object:\n  Inverter -> negated Inverter\n  Polarity -> opposite Polarity\n  Toggle -> flipped Toggle\n\nCORRECT PATTERN\n  operator ~ as pure\n    <- rtn as Inverter: Inverter()\nThe return type (Inverter) matches the enclosing class (Inverter).\n\nINCORRECT PATTERN\n  operator ~ as pure\n    <- rtn as Float: 1.0\nReturning Float from an Inverter class triggers E07410 because Float is not Inverter.\n\nUSAGE\n  original <- Inverter(true)\n  negated <- ~original  // negated is an Inverter\n\nSee Q238 for operator overview. See Q839 for promote operator (which must return a DIFFERENT type).","ek9Example":"defines module qa.operators.negate.same.type\n\n  defines class\n\n    <?-\n      Inverter with negate operator returning the same type.\n      This is the correct pattern for negation.\n    -?>\n    Inverter\n      active <- true\n\n      Inverter() as pure\n        -> initialState as Boolean\n        active :=: initialState\n\n      operator ~ as pure\n        <- rtn as Inverter: Inverter(active)\n\n      operator $ as pure\n        <- rtn as String: `active: ${active}`\n\n      default operator ?\n\n  defines program\n\n    NegateDemo()\n      stdout <- Stdout()\n      original <- Inverter(true)\n      negated <- ~original\n      stdout.println($negated)","migrationContext":"Java: No negate operator for custom types; bitwise ~ returns int/long. Python: __invert__ can return any type (no enforcement). Rust: Not trait requires Output=Self by convention but not enforced by compiler. Kotlin: operator fun not() can return any type. EK9: operator ~ enforces return type matches enclosing class at compile time.","keywords":["E07410","construct","negate","operator","return","same","type","~"],"primaryTopics":["negate operator","E07410","MUST_RETURN_SAME_AS_CONSTRUCT_TYPE"],"typicalErrors":[{"error":"E07410","correct":"      operator ~ as pure\n        <- rtn as Inverter: Inverter(active)","incorrect":"      operator ~ as pure\n        <- rtn as Float: 1.0","explanation":"The negate operator ~ must return the same type as the enclosing class. Returning Float from an Inverter class is not valid negation. Return an Inverter instead. See ek9 -h E07410 for details."}],"companions":[]}
{"id":865,"category":"Code Quality","question":"Why does EK9 flag checking empty on a freshly constructed list?","url":"https://ek9.io/qa/QA0865.html","alternatePhrasings":["What triggers E08092 REDUNDANT_EMPTY_CHECK?","Why is checking empty right after creating a list redundant?","How does EK9 detect tautological empty checks?"],"answer":"EK9 uses data flow analysis to detect redundant empty checks. A freshly created collection is always empty, so checking 'if items empty' immediately after creation is a tautology — the condition is always true.\n\nREDUNDANT (E08092)\n  items <- List() of String\n  if items empty           // ALWAYS true — nothing was added\n    result: \"nothing here\"\n\nVALID CHECK\n  items <- List() of String\n  items += \"hello\"\n  if items empty           // valid — state changed since creation\n    result: \"nothing here\"\n\nAdding items between creation and the empty check makes the check meaningful because the collection state is no longer provably empty.\n\nSee Q820 for redundant empty check overview. See Q310 for code quality.","ek9Example":"defines module qa.quality.redundant.empty\n\n  defines function\n\n    checkEmptiness()\n      -> greeting as String\n      <- result as String: String()\n\n      items <- List() of String\n      items += greeting\n      if items empty\n        result: \"nothing here\"\n      else\n        result: \"has content\"\n\n  defines program\n\n    RedundantEmptyDemo()\n      stdout <- Stdout()\n\n      outcome <- checkEmptiness(\"hello\")\n      stdout.println(`Result: ${outcome}`)","migrationContext":"Java: no detection — isEmpty() on new ArrayList compiles silently. Python: no detection. Rust: no detection. Go: no detection. EK9: compile-time error for provably redundant empty checks via data flow analysis.","keywords":["E08092","collection","data","empty","flow","list","redundant","tautology"],"primaryTopics":["redundant empty check","E08092","REDUNDANT_EMPTY_CHECK"],"typicalErrors":[{"error":"E08092","correct":"      items += greeting\n      if items empty","incorrect":"      if items empty","explanation":"Without adding any items, the list is provably empty. Checking 'if items empty' is always true — the compiler detects this tautology. Add items before checking. See ek9 -h E08092 for details."}],"companions":[]}
{"id":866,"category":"Streams and Pipelines","question":"Why must functions used with stream call return a value?","url":"https://ek9.io/qa/QA0866.html","alternatePhrasings":["What triggers E07490 FUNCTION_MUST_RETURN_VALUE in streams?","Why can't I use a void function with cat and call?","What happens when a function in a stream pipeline returns nothing?"],"answer":"Functions used with call in a stream pipeline must return a value. The return value becomes the next element flowing downstream. A void function produces nothing, breaking the pipeline.\n\nCORRECT PATTERN\n  getSteve()\n    <- rtn <- \"Steve\"\n  cat [getSteve] | call > collector\nThe function returns a String, which flows into the collector.\n\nINCORRECT PATTERN\n  doesNotReturn()\n    require true\n  cat [doesNotReturn] | call > collector\nThe void function produces nothing — the pipeline has no elements to collect.\n\nSee Q843 for function return requirement. See Q813 for call/async rules.","ek9Example":"defines module qa.streams.call.void.function\n\n  defines function\n\n    getSteve()\n      <- rtn <- \"Steve\"\n\n    doesNotReturn()\n      require true\n\n  defines class\n    StringCollector\n      received <- String()\n      operator |\n        -> item as String\n        if item?\n          received: String(item)\n      override operator ? as pure\n        <- rtn as Boolean: received?\n\n  defines function\n\n    StreamCallDemo()\n      collector <- StringCollector()\n      cat [getSteve] | call > collector\n      require collector?","migrationContext":"Java: Supplier returns T, Runnable returns void — Stream.generate requires Supplier. Python: map expects return values. EK9: call requires supplier-style functions that return a value.","keywords":["E07490","call","function","pipeline","return","stream","supplier","void"],"primaryTopics":["stream function return","E07490","FUNCTION_MUST_RETURN_VALUE"],"typicalErrors":[{"error":"E07490","correct":"      cat [getSteve] | call > collector","incorrect":"      cat [doesNotReturn] | call > collector","explanation":"Functions used with stream call must return a value. The void function doesNotReturn produces nothing for the pipeline. Use a function that returns a value. See ek9 -h E07490 for details."}],"companions":[]}
{"id":867,"category":"Streams and Pipelines","question":"Why must functions used with stream call take zero arguments?","url":"https://ek9.io/qa/QA0867.html","alternatePhrasings":["What triggers E06310 REQUIRE_NO_ARGUMENTS in streams?","Why can't I stream a function that takes parameters through call?","What is the correct function shape for stream call?"],"answer":"Functions used with call in a stream pipeline must take ZERO arguments. The stream invokes them without parameters — they act as suppliers.\n\nCORRECT PATTERN\n  getGreeting()\n    <- rtn <- \"Hello EK9\"\n  cat [getGreeting] | call > collector\nZero-argument function that returns a value.\n\nINCORRECT PATTERN\n  needsArg()\n    -> prefix as String\n    <- rtn <- \"Hello EK9\"\n  cat [needsArg] | call > collector\nThe function takes a parameter — the stream has no way to supply it.\n\nSee Q844 for call argument rules. See Q843 for return requirement.","ek9Example":"defines module qa.streams.call.zero.args\n\n  defines function\n\n    getGreeting()\n      <- rtn <- \"Hello EK9\"\n\n    needsArg()\n      -> prefix as String\n      <- rtn as String: prefix\n\n  defines class\n    StringCollector\n      received <- String()\n      operator |\n        -> item as String\n        if item?\n          received: String(item)\n      override operator ? as pure\n        <- rtn as Boolean: received?\n\n  defines function\n\n    StreamZeroArgsDemo()\n      collector <- StringCollector()\n      cat [getGreeting] | call > collector\n      require collector?","migrationContext":"Java: Supplier has get() with no arguments. Python: zero-arg callables used with map/filter. EK9: call requires zero-argument supplier functions.","keywords":["E06310","arguments","call","function","pipeline","stream","supplier","zero"],"primaryTopics":["stream call arguments","E06310","REQUIRE_NO_ARGUMENTS"],"typicalErrors":[{"error":"E06310","correct":"      cat [getGreeting] | call > collector","incorrect":"      cat [needsArg] | call > collector","explanation":"Functions used with stream call must take no arguments. The function needsArg requires a parameter the stream cannot supply. Use a zero-argument function. See ek9 -h E06310 for details."}],"companions":[]}
{"id":868,"category":"Web Services","question":"Why can't I use HTTPRequest as a path parameter type in EK9 services?","url":"https://ek9.io/qa/QA0868.html","alternatePhrasings":["What triggers E07790 SERVICE_INCOMPATIBLE_PARAM_TYPE_NON_REQUEST?","When is HTTPRequest invalid as a service parameter type?","How do I correctly bind service path parameters?"],"answer":"HTTPRequest can only be used with :=: REQUEST binding. Using HTTPRequest as a PATH parameter type triggers E07790 because path parameters are parsed from URI segments — they must be simple types like Integer or String.\n\nCORRECT PATTERN\n  findById() as GET for :/{userId}\n    -> userId as Integer\nInteger is a valid path parameter type.\n\nINCORRECT PATTERN\n  findById() as GET for :/{userId}\n    -> userId as HTTPRequest\nHTTPRequest cannot be bound to a PATH segment. Use :=: REQUEST binding if you need the full request.\n\nVALID PATH PARAMETER TYPES\nInteger, String, Date, Duration, DateTime, Time, Millisecond.\n\nSee Q846 for parameter type rules. See Q202 for parameter binding.","ek9Example":"defines module qa.webdeep.service.request.param.type\n\n  defines constant\n\n    JSON_RESPONSE <- \"application/json\"\n\n  defines service\n\n    <?-\n      Service with valid Integer path parameter.\n      Path parameters must be simple parseable types.\n    -?>\n    AccountService :/accounts open\n\n      findById() as GET for :/{userId}\n        -> userId as Integer\n        <- response as HTTPResponse: (capturedId: userId) with trait HTTPResponse\n          override content()\n            <- rtn as String: `{\"userId\": ${capturedId}}`\n          override status() as pure\n            <- rtn as Integer: 200\n          override contentType() as pure\n            <- rtn as String: JSON_RESPONSE\n          override cacheControl() as pure\n            <- rtn as String: \"no-cache\"\n          override contentLanguage() as pure\n            <- rtn as String: \"en\"\n          default operator ?\n\n  defines application\n\n    AccountApp\n      register AccountService()\n\n  defines program\n\n    ServiceParamTypeDemo()\n      stdout <- Stdout()\n      stdout.println(\"Valid path param types: Integer, String, Date, Time\")\n      stdout.println(\"Invalid: HTTPRequest (use :=: REQUEST binding instead)\")","migrationContext":"Java Spring: @PathVariable binds to any type with a converter. C# ASP.NET: model binding to complex types. Go: manual string parsing. Python Flask: parameter converters at runtime. EK9: compile-time restriction prevents HTTPRequest from being used as a path or query parameter.","keywords":["E07790","HTTPRequest","Integer","binding","parameter","path","service","type"],"primaryTopics":["service parameter types","E07790","SERVICE_INCOMPATIBLE_PARAM_TYPE_NON_REQUEST"],"typicalErrors":[{"error":"E07790","correct":"        -> userId as Integer","incorrect":"        -> userId as HTTPRequest","explanation":"HTTPRequest cannot be used as a path parameter type. Path parameters are parsed from URI segments and must be simple types like Integer or String. Use :=: REQUEST binding for HTTPRequest. See ek9 -h E07790 for details."}],"companions":[]}
{"id":869,"category":"Web Services","question":"Why must all EK9 service methods declare a return value?","url":"https://ek9.io/qa/QA0869.html","alternatePhrasings":["What triggers E07800 SERVICE_MISSING_RETURN?","Why can't my service method omit the return declaration?","What return type do EK9 service methods require?"],"answer":"ALL EK9 service methods must have a return declaration. Services are HTTP endpoints and must return an HTTPResponse to express status codes, headers, and content. A method without a return declaration triggers E07800.\n\nCORRECT PATTERN\n  healthCheck() as GET for :/status\n    <- response as HTTPResponse: () with trait HTTPResponse\n      ...\nThe method declares a return of HTTPResponse.\n\nINCORRECT PATTERN\n  healthCheck() as GET for :/status\n    require true\nNo return declaration — the compiler cannot determine what HTTP response to send.\n\nSee Q845 for service return type. See Q659 for service return patterns.","ek9Example":"defines module qa.webdeep.service.needs.return\n\n  defines service\n\n    <?-\n      Service with proper return declaration.\n      All service methods must return HTTPResponse.\n    -?>\n    StatusService :/api open\n\n      healthCheck() as GET for :/status\n        <- response as HTTPResponse: () with trait HTTPResponse\n          override content()\n            <- rtn as String: `{\"status\": \"healthy\"}`\n          override status() as pure\n            <- rtn as Integer: 200\n          override contentType() as pure\n            <- rtn as String: \"application/json\"\n          override cacheControl() as pure\n            <- rtn as String: \"no-cache\"\n          override contentLanguage() as pure\n            <- rtn as String: \"en\"\n          default operator ?\n\n  defines application\n\n    StatusApp\n      register StatusService()\n\n  defines program\n\n    ServiceReturnDemo()\n      stdout <- Stdout()\n      stdout.println(\"All service methods must declare a return value\")\n      stdout.println(\"The return type must be compatible with HTTPResponse\")","migrationContext":"Java Spring: void controllers write to response directly. C# ASP.NET: void actions with Response.Write. Go: handlers write to ResponseWriter without return. Python Flask: can return None (500 error). EK9: mandatory HTTPResponse return ensures every endpoint has a well-defined response.","keywords":["E07800","HTTPResponse","endpoint","method","missing","return","service"],"primaryTopics":["service return type","E07800","SERVICE_MISSING_RETURN"],"typicalErrors":[{"error":"E07800","correct":"      healthCheck() as GET for :/status\n        <- response as HTTPResponse: () with trait HTTPResponse\n          override content()\n            <- rtn as String: `{\"status\": \"healthy\"}`\n          override status() as pure\n            <- rtn as Integer: 200\n          override contentType() as pure\n            <- rtn as String: \"application/json\"\n          override cacheControl() as pure\n            <- rtn as String: \"no-cache\"\n          override contentLanguage() as pure\n            <- rtn as String: \"en\"\n          default operator ?","incorrect":"      healthCheck() as GET for :/status\n        require true","explanation":"Service methods must always return an HTTPResponse. Omitting the return declaration means the compiler cannot determine the HTTP response. See ek9 -h E07800 for details."}],"companions":[]}
{"id":870,"category":"Dispatcher Validation","question":"How does adding a concrete type handler resolve dispatcher ambiguity?","url":"https://ek9.io/qa/QA0870.html","alternatePhrasings":["What triggers E05230 DISPATCHER_HANDLER_AMBIGUITY?","How do I fix dispatcher ambiguity for types implementing multiple traits?","Why does replacing a concrete handler with a trait handler cause ambiguity?"],"answer":"When a class implements two traits and a dispatcher has handlers for both traits, dispatching that class is ambiguous (E05230) because both handlers match at equal cost (0.10 each). Adding a handler for the concrete type resolves the ambiguity because exact match costs 0.00.\n\nTHE PROBLEM\nDuck implements Flyable and Swimmable. Handlers exist for both traits at cost 0.10 each. Dispatching Duck is ambiguous.\n\nTHE SOLUTION\nAdd a handler specifically for Duck (cost 0.00 — exact match). This always wins over trait handlers.\n\nCORRECT PATTERN\n  process()\n    -> animal as Duck\n    <- rtn as String: animal.speak()\nExact match handler at cost 0.00 beats trait handlers at cost 0.10.\n\nINCORRECT PATTERN (removes Duck handler)\nWithout the Duck handler, Duck matches both Flyable (0.10) and Swimmable (0.10) at equal cost. Ambiguity.\n\nSee Q616 for ambiguity basics. See Q617 for diamond trait dispatch.","ek9Example":"defines module qa.dispatchervalidation.ambiguity.resolution\n\n  defines trait\n\n    Flyable\n      fly() as abstract\n        <- rtn as String?\n\n    Swimmable\n      swim() as abstract\n        <- rtn as String?\n\n  defines class\n\n    Animal as abstract\n      speak() as abstract\n        <- rtn as String?\n      default operator ?\n\n    Dog extends Animal\n      override speak()\n        <- rtn as String: \"woof\"\n\n    Duck extends Animal with trait of Flyable, Swimmable\n      override speak()\n        <- rtn as String: \"quack\"\n      override fly()\n        <- rtn as String: \"flapping\"\n      override swim()\n        <- rtn as String: \"paddling\"\n      override operator ? as pure\n        <- rtn as Boolean: true\n\n    <?-\n      Dispatcher with handlers for Animal (base), Flyable, Swimmable,\n      and Duck (concrete). The Duck handler resolves ambiguity.\n    -?>\n    AnimalProcessor\n      process() as dispatcher\n        -> animal as Animal\n        <- rtn as String: animal.speak()\n\n      process()\n        -> animal as Dog\n        <- rtn as String: animal.speak()\n\n      process()\n        -> creature as Flyable\n        <- rtn as String: creature.fly()\n\n      process()\n        -> creature as Swimmable\n        <- rtn as String: creature.swim()\n\n      //Duck handler resolves ambiguity (cost 0.00 beats trait cost 0.10)\n      process()\n        -> animal as Duck\n        <- rtn as String: animal.speak()\n\n  defines function\n\n    testAmbiguityResolution()\n      processor <- AnimalProcessor()\n      dog <- Dog()\n      duck <- Duck()\n      require processor.process(dog) == \"woof\"\n      require processor.process(duck) == \"quack\"","migrationContext":"Java: method overloading resolved at compile time, no runtime dispatch ambiguity. C++: multiple inheritance ambiguity resolved with virtual base classes. Python: C3 linearization provides deterministic ordering. Kotlin: compiler error for interface default method conflicts. EK9: E05230 detects dispatch ambiguity at compile time, requires structural resolution.","keywords":["E05230","ambiguity","concrete","cost","dispatcher","duck","handler","resolution","trait"],"primaryTopics":["dispatcher ambiguity","E05230","DISPATCHER_HANDLER_AMBIGUITY"],"typicalErrors":[{"error":"E05230","correct":"      //Duck handler resolves ambiguity (cost 0.00 beats trait cost 0.10)\n      process()\n        -> animal as Duck\n        <- rtn as String: animal.speak()","incorrect":"      //Duck handler removed — ambiguity not resolved","explanation":"Without the Duck-specific handler, Duck matches both Flyable and Swimmable at equal cost (0.10 each). The dispatcher cannot choose. Add a concrete type handler to resolve. See ek9 -h E05230 for details."}],"companions":[]}
{"id":871,"category":"Getting Started","question":"How do I print a message to the console in EK9?","url":"https://ek9.io/qa/QA0871.html","alternatePhrasings":["How do I output text in EK9?","What is the EK9 equivalent of System.out.println?","How do I write to stdout in EK9?"],"answer":"In Java you write System.out.println(), in Python you write print(). In EK9 the equivalent is:\n\n  stdout <- Stdout()\n  stdout.println(\"your message\")\n\nThe '<-' creates a local variable called stdout that holds a reference to standard output. Then you call println() on it. This two-step pattern — create the output stream, then write to it — appears in every EK9 program.\n\nYou must wrap this inside 'defines module' and 'defines program' blocks. See Q1 for the full structure. Use 'ek9 -h Stdout' for all available output methods.","ek9Example":"defines module qa.getting.started.hello.variant\n\n  defines program\n\n    PrintMessage()\n      stdout <- Stdout()\n      stdout.println(\"Welcome to EK9\")\n      stdout.println(\"Programming is fun\")","migrationContext":"Java: System.out.println(). Python: print(). Go: fmt.Println(). EK9: Stdout().println().","keywords":["console","message","output","print","println","stdout","text"],"primaryTopics":["console output","Stdout","println"],"typicalErrors":[],"companions":[]}
{"id":872,"category":"Getting Started","question":"How do I declare variables and use string interpolation in an EK9 program?","url":"https://ek9.io/qa/QA0872.html","alternatePhrasings":["Show me variable declaration with interpolated strings in EK9","How do backtick strings work in EK9?","EK9 string templates with variables"],"answer":"Declare variables with '<-' (type is inferred). Use backtick strings with ${expression} for interpolation. EK9 infers the type from the assigned value — no need to declare types explicitly for locals.\n\nSee Q37 for strings. See Q22 for variable declaration. Use 'ek9 -h String' for string methods.","ek9Example":"defines module qa.getting.started.variables\n\n  defines program\n\n    VariableDemo()\n      stdout <- Stdout()\n\n      greeting <- \"Hello\"\n      userName <- \"Steve\"\n      age <- 42\n\n      stdout.println(`${greeting}, ${userName}!`)\n      stdout.println(`You are ${age} years old`)","migrationContext":"Java: var keyword (Java 10+). Python: dynamic typing. Kotlin: val/var. EK9: '<-' with type inference.","keywords":["backtick","declaration","inference","interpolation","string","template","variable"],"primaryTopics":["variable declaration","string interpolation","type inference"],"typicalErrors":[],"companions":[],"oracleToolHint":{"tool":"ek9_scaffold","intent":"program","description":"Oracle can generate a program construct with variable declarations and string interpolation."}}
{"id":873,"category":"Functions and Methods","question":"How do I write a pure function in EK9 that takes parameters and returns a result?","url":"https://ek9.io/qa/QA0873.html","alternatePhrasings":["Show me a function with input and output in EK9","What does 'as pure' mean on a function?","EK9 function with parameters and return value"],"answer":"Functions use '->' for input parameters and '<-' for the return value. Mark functions 'as pure' when they have no side effects — pure functions only compute from their inputs.\n\nThe return variable is declared on the '<-' line and automatically returned. EK9 has no 'return' statement — the compiler ensures all paths initialise the return variable.\n\nSee Q49 for function basics. See Q560 for purity contracts. Use 'ek9 -h function' for syntax.","ek9Example":"defines module qa.functions.pure.basics\n\n  defines constant\n\n    ADULT_AGE <- 18\n\n  defines function\n\n    calculateArea() as pure\n      ->\n        width as Float\n        height as Float\n      <-\n        rtn as Float: width * height\n\n    isAdult() as pure\n      -> age as Integer\n      <- rtn as Boolean: age >= ADULT_AGE\n\n    formatGreeting() as pure\n      -> personName as String\n      <- rtn as String: `Hello, ${personName}!`\n\n  defines program\n\n    FunctionDemo()\n      stdout <- Stdout()\n\n      area <- calculateArea(5.0, 3.0)\n      stdout.println(`Area: ${area}`)\n\n      stdout.println(`Adult: ${isAdult(21)}`)\n      stdout.println(formatGreeting(\"Steve\"))","migrationContext":"Java: methods with return type. Python: def with return. Rust: fn with ->. EK9: '<-' declares return, 'as pure' enforces no side effects.","keywords":["function","input","output","parameter","pure","return","side-effect"],"primaryTopics":["pure functions","parameters","return values"],"typicalErrors":[],"companions":[],"oracleToolHint":{"tool":"ek9_scaffold","intent":"function","description":"Oracle can generate a pure function with parameters and return value."}}
{"id":874,"category":"Classes and OOP","question":"How do I create a class with a constructor and methods in EK9?","url":"https://ek9.io/qa/QA0874.html","alternatePhrasings":["Show me a basic EK9 class with state and behaviour","How do I define a class with fields and methods?","EK9 class example with constructor"],"answer":"Classes have private fields (initialised with '<-'), constructors, and methods. Fields are always private in classes. Use ':=:' (copy) to assign constructor parameters to fields. Add 'default operator ?' at the end for the isSet check.\n\nSee Q93 for class basics. See Q94 for constructors. See Q96 for operators. Use 'ek9 -h class' for syntax.","ek9Example":"defines module qa.classes.with.methods\n\n  defines class\n\n    BankAccount\n      ownerName <- String()\n      balance <- 0.0\n\n      BankAccount()\n        ->\n          owner as String\n          initialBalance as Float\n        ownerName :=: owner\n        balance :=: initialBalance\n\n      deposit()\n        -> amount as Float\n        balance += amount\n\n      getBalance()\n        <- rtn as Float: Float(balance)\n\n      describe()\n        <- rtn as String: `${ownerName}: balance ${balance}`\n\n      default operator ?\n\n  defines program\n\n    BankDemo()\n      stdout <- Stdout()\n\n      account <- BankAccount(\"Steve\", 100.0)\n      account.deposit(50.0)\n\n      if account?\n        stdout.println(account.describe())\n        stdout.println(`Balance: ${account.getBalance()}`)","migrationContext":"Java: public/private fields. Python: self.field. Kotlin: data class. EK9: fields always private, '<-' initialises, ':=:' copies.","keywords":["behaviour","class","constructor","field","method","private","state"],"primaryTopics":["class definition","constructors","methods"],"typicalErrors":[],"companions":[],"oracleToolHint":{"tool":"ek9_scaffold","intent":"class","description":"Oracle can generate a class with constructor, methods, and operator declarations."}}
{"id":875,"category":"Getting Started","question":"What is the structure of an EK9 source file?","url":"https://ek9.io/qa/QA0875.html","alternatePhrasings":["What keywords do I need for an EK9 program?","Why do I need defines module and defines program?","Show me the skeleton of an EK9 file"],"answer":"Every EK9 file starts with '#!ek9' then 'defines module <name>'. Inside the module you place sections like 'defines program', 'defines function', 'defines class', 'defines record'. A runnable program needs 'defines program' with a named entry point. Indentation (2 spaces) defines scope — no braces or semicolons.\n\nSee Q1 for minimal program. See Q5 for file structure. See Q17 for entry points.","ek9Example":"defines module qa.getting.started.structure\n\n  defines function\n\n    greet() as pure\n      -> who as String\n      <- rtn as String: `Hello, ${who}!`\n\n  defines program\n\n    ShowStructure()\n      stdout <- Stdout()\n      message <- greet(\"EK9 developer\")\n      stdout.println(message)","migrationContext":"Java: package + class + main method. Python: just code. Go: package main + func main. EK9: defines module + defines program + named entry.","keywords":["defines","file","indent","module","program","skeleton","structure"],"primaryTopics":["file structure","defines module","defines program"],"typicalErrors":[],"companions":[]}
{"id":876,"category":"Classes and OOP","question":"How do I define a record with fields in EK9?","url":"https://ek9.io/qa/QA0876.html","alternatePhrasings":["What is the syntax for EK9 records?","How are records different from classes in EK9?","Show me a simple EK9 record"],"answer":"If you are coming from Java, think Java record. From Rust, think struct. From Kotlin, think data class.\n\nEK9 records are pure data containers. Their fields are PUBLIC (unlike class fields which are always private). They cannot have regular methods — only constructors and operators.\n\nIMPORTANT RULES\n- Every field needs a default value: 'xPos <- 0.0' not 'xPos as Float'\n- Add 'operator <=>' (comparator) then 'default operator' to auto-generate ==, <>, $, #?\n- Use ':=:' in constructors to copy parameter values to fields\n\nSee Q97 for class vs record differences. See Q98 for record operators. Use 'ek9 -h record' for full syntax.","ek9Example":"defines module qa.classes.record.basics\n\n  defines record\n\n    Coordinate\n      xPos <- 0.0\n      yPos <- 0.0\n\n      Coordinate()\n        ->\n          initialX as Float\n          initialY as Float\n        xPos :=: initialX\n        yPos :=: initialY\n\n      operator <=> as pure\n        -> arg0 as Coordinate\n        <- rtn as Integer: xPos <=> arg0.xPos\n\n      default operator\n\n  defines program\n\n    RecordDemo()\n      stdout <- Stdout()\n\n      origin <- Coordinate(0.0, 0.0)\n      point <- Coordinate(3.0, 4.0)\n\n      if point?\n        stdout.println(\"Point: \" + $point)\n        stdout.println(`Equal: ${origin == point}`)","migrationContext":"Java: record keyword (Java 16+). Kotlin: data class. Python: dataclass. Rust: struct. EK9: defines record with public fields and auto-generated operators.","keywords":["data","default","field","operator","public","record","struct"],"primaryTopics":["record definition","record fields","record vs class"],"typicalErrors":[],"companions":[],"oracleToolHint":{"tool":"ek9_scaffold","intent":"record","description":"Oracle can generate a record with public fields, constructors, and default operator declarations."}}
{"id":877,"category":"Operators and Expressions","question":"What does the '?' operator do in EK9 and what does 'default operator ?' mean?","url":"https://ek9.io/qa/QA0877.html","alternatePhrasings":["How does isSet work in EK9?","What is the difference between set and unset in EK9?","How do I check if a variable has a value in EK9?"],"answer":"Forget null. EK9 has no null. Instead it has three states: absent, unset, and set.\n\nThe '?' operator is the isSet check — it returns true when a value has meaningful data:\n  name <- \"Steve\"    // name? returns true (set)\n  empty <- String()   // empty? returns false (unset)\n  if name?\n    stdout.println(name)  // safe — we checked first\n\nFor your own classes, add 'default operator ?' at the end of the class. The compiler generates the check from your fields. Or write 'override operator ?' for custom logic.\n\nThe ':=?' operator assigns only if the target is currently unset — perfect for defaults:\n  config :=? \"fallback\"  // only assigns if config is unset\n\nThis is NOT null coalescing. There is no null. See Q29 for tri-state details. See Q84 for Optional.","ek9Example":"defines module qa.operators.isset\n\n  defines class\n\n    UserProfile\n      userName <- String()\n      email <- String()\n\n      UserProfile()\n        ->\n          name as String\n          addr as String\n        userName :=: name\n        email :=: addr\n\n      describe()\n        <- rtn as String: `${userName} <${email}>`\n\n      default operator ?\n\n  defines program\n\n    IsSetDemo()\n      stdout <- Stdout()\n\n      //Set profile — '?' returns true\n      profile <- UserProfile(\"Steve\", \"steve@example.com\")\n      if profile?\n        stdout.println(profile.describe())\n\n      //Unset string — '?' returns false\n      unsetName <- String()\n      if not unsetName?\n        stdout.println(\"Name is unset\")\n\n      //Guard assignment — only assigns if unset\n      unsetName :=? \"default name\"\n      stdout.println(unsetName)","migrationContext":"EK9 replaces null checks from other languages with the ? suffix operator. Append ? after any variable to check if it holds a value.","keywords":["check","default","guard","isset","operator","question","set","tristate","unset"],"primaryTopics":["isSet operator","tri-state","default operator ?"],"typicalErrors":[],"companions":[]}
{"id":878,"category":"Classes and OOP","question":"How does EK9 handle return values without a return statement?","url":"https://ek9.io/qa/QA0878.html","alternatePhrasings":["Why doesn't EK9 have a return keyword?","How do I return a value from a function in EK9?","What is the '<-' return declaration in EK9?"],"answer":"Look at this function — notice there is no 'return' keyword anywhere:\n\n  maxOfTwo() as pure\n    -> firstNum as Integer, secondNum as Integer\n    <- rtn as Integer: firstNum\n    if secondNum > firstNum\n      rtn: secondNum\n\nThe '<- rtn as Integer: firstNum' line DECLARES the return variable and gives it an initial value. The compiler then checks that ALL possible code paths leave 'rtn' with a valid value. If any path could leave it uninitialised, the code won't compile.\n\nThis design eliminates an entire category of bugs: forgotten returns, early returns bypassing cleanup, and unreachable code after return. The 'return' keyword simply does not exist in EK9's grammar.\n\nSee Q146 for the design rationale. See Q144 for no break/continue/return.","ek9Example":"defines module qa.classes.no.return\n\n  defines function\n\n    maxOfTwo() as pure\n      ->\n        firstNum as Integer\n        secondNum as Integer\n      <- rtn as Integer: firstNum\n\n      //All paths must set rtn — compiler enforces this\n      if secondNum > firstNum\n        rtn: secondNum\n\n  defines class\n\n    Formatter\n      prefix <- \"[\"\n      suffix <- \"]\"\n\n      default Formatter()\n\n      Formatter()\n        ->\n          p as String\n          s as String\n        prefix :=: p\n        suffix :=: s\n\n      format()\n        -> text as String\n        <- rtn as String: `${prefix}${text}${suffix}`\n\n      default operator ?\n\n  defines program\n\n    ReturnDemo()\n      stdout <- Stdout()\n\n      bigger <- maxOfTwo(10, 20)\n      stdout.println(`Max: ${bigger}`)\n\n      formatter <- Formatter(\"<\", \">\")\n      if formatter?\n        stdout.println(formatter.format(\"hello\"))","migrationContext":"Java: return keyword required. Python: return keyword. Go: return keyword. EK9: '<-' declares return variable, no return statement exists.","keywords":["arrow","declaration","flow","initialise","no-return","return","variable"],"primaryTopics":["return declaration","no return statement","<- syntax"],"typicalErrors":[],"companions":[]}
{"id":879,"category":"Getting Started","question":"What is the difference between <-, :=, and :=: in EK9?","url":"https://ek9.io/qa/QA0879.html","alternatePhrasings":["EK9 has too many assignment operators — which one do I use?","When do I use := vs :=: vs <- in EK9?","Explain the arrow and colon operators in EK9"],"answer":"Three operators, three purposes:\n\n  name <- \"Steve\"        // DECLARE: creates new variable, type inferred\n  name := \"Different\"    // ASSIGN: changes existing variable\n  other :=: name          // COPY: deep copies value from name into other\n\nThink of it this way:\n- '<-' is for BIRTH — the variable didn't exist before this line\n- ':=' is for CHANGE — the variable already exists, you're updating it\n- ':=:' is for CLONING — copy the contents, not the reference\n\nThere are also two conditional variants:\n  config :=? \"default\"   // assign only if config is currently UNSET\n  if guard ?= getValue()  // guard: execute block only if result is SET\n\nCommon mistake from Java/Python developers: using '<-' to reassign. This creates a NEW variable that shadows the old one. Use ':=' to change an existing variable.\n\nSee Q22 for variable declaration. See Q29 for unset variables. See Q75 for guard expressions.","ek9Example":"defines module qa.getting.started.three.operators\n\n  defines program\n\n    ThreeOperators()\n      stdout <- Stdout()\n\n      //DECLARE with <-\n      counter <- 0\n      stdout.println(`Created counter: ${counter}`)\n\n      //ASSIGN with :=\n      counter := 10\n      stdout.println(`Changed counter: ${counter}`)\n\n      //COPY with :=:\n      backup <- 0\n      backup :=: counter\n      counter := 99\n      stdout.println(`Counter now: ${counter}`)\n      stdout.println(`Backup is still: ${backup}`)\n\n      //CONDITIONAL with :=?\n      serverName <- String()\n      serverName :=? \"localhost\"\n      stdout.println(`Server: ${serverName}`)","migrationContext":"Java: single '=' for everything. Python: single '=' for everything. Rust: 'let' for declare, '=' for assign. EK9: '<-' declare, ':=' assign, ':=:' deep copy — each has distinct semantics.","keywords":["arrow","assign","colon","copy","declare","difference","operator","variable"],"primaryTopics":["<- operator",":= operator",":=: operator","assignment"],"typicalErrors":[],"companions":[]}
{"id":880,"category":"Getting Started","question":"What features from other languages does EK9 deliberately exclude?","url":"https://ek9.io/qa/QA0880.html","alternatePhrasings":["What can't I do in EK9 that I can in Java or Python?","Why is EK9 missing break continue and return?","What keywords don't exist in EK9?"],"answer":"EK9 DELIBERATELY EXCLUDES these features. They are not missing — they were removed based on decades of production bug evidence:\n\nNO break — use 'head N' in streams or guard expressions instead\nNO continue — use 'filter by' in streams instead\nNO return — declare the return variable with '<-', compiler ensures all paths set it\nNO null — EK9 uses tri-state (absent/unset/set) with the '?' operator\nNO type casts — use promotion '#^' or explicit conversion\nNO switch fallthrough — each case is independent, use comma-separated values\nNO public keyword — methods are public by default, 'public' is redundant and rejected\nNO new keyword — constructors are called directly: 'MyClass()' not 'new MyClass()'\nNO semicolons — indentation defines scope\nNO braces — indentation defines scope\n\nThe grammar literally does not contain these keywords. You cannot write 'break' — the parser will reject it.\n\nSee Q144 for no break/continue/return. See Q145 for replacements. See Q29 for no null.","ek9Example":"defines module qa.getting.started.exclusions\n\n  defines constant\n\n    TEEN_AGE <- 13\n    ADULT_AGE <- 18\n\n  defines function\n\n    classifyAge() as pure\n      -> years as Integer\n      <- rtn as String: \"unknown\"\n\n      //No 'return' — set rtn on each path\n      if years < TEEN_AGE\n        rtn: \"child\"\n      else if years < ADULT_AGE\n        rtn: \"teenager\"\n      else\n        rtn: \"adult\"\n\n  defines program\n\n    ExclusionsDemo()\n      stdout <- Stdout()\n\n      //No 'new' keyword — just call the constructor\n      names <- List() of String\n      names += \"Steve\"\n      names += \"Limb\"\n      stdout.println(`Names: ${length names}`)\n\n      //No 'null' — use ? to check\n      unsetName <- String()\n      if not unsetName?\n        stdout.println(\"Name is unset, not null\")\n\n      stdout.println(classifyAge(25))","migrationContext":"All languages: these features exist everywhere else. EK9: removed from grammar entirely. Not deprecated, not warned — gone.","keywords":["break","cast","continue","design","exclude","fallthrough","missing","null","return"],"primaryTopics":["excluded features","design decisions","no break","no return","no null"],"typicalErrors":[],"companions":[]}
{"id":881,"category":"Getting Started","question":"I keep getting errors mixing up <- and := in EK9. What is the rule?","url":"https://ek9.io/qa/QA0881.html","alternatePhrasings":["When do I use the arrow <- vs the colon-equals := in EK9?","My variable already exists but <- creates a new one — help!","EK9 variable shadowing with <- operator"],"answer":"Simple rule: '<-' is for the FIRST TIME. ':=' is for EVERY TIME AFTER.\n\n  score <- 100      // first time: score is born here\n  score := 200      // after: score already exists, update it\n  score <- 300      // WRONG: creates a NEW variable, shadows the old one\n\nIf you use '<-' on a variable that already exists in an outer scope, you create a NEW local variable with the same name. The outer variable is unchanged. This is almost always a bug.\n\nThe compiler may warn you with E11031 if the variable name is too generic, or E08090 if the shadowed variable is never used after the shadowing declaration.\n\nFor function returns, '<-' declares the return variable:\n  calculateTotal() as pure\n    -> items as List of Integer\n    <- total as Integer: 0    // '<-' declares return\n    for item in items\n      total += item            // ':=' implicit via +=\n\nSee Q22 for declaration rules. See Q879 for all three assignment operators.","ek9Example":"defines module qa.getting.started.declare.vs.assign\n\n  defines function\n\n    sumList()\n      -> numbers as List of Integer\n      <- total as Integer: 0\n\n      for item in numbers\n        total += item\n\n  defines program\n\n    DeclareVsAssign()\n      stdout <- Stdout()\n\n      //Correct: declare then assign\n      message <- \"hello\"\n      message := \"updated\"\n      stdout.println(message)\n\n      //Demonstrate with a list\n      scores <- List() of Integer\n      scores += 10\n      scores += 20\n      scores += 30\n\n      result <- sumList(scores)\n      stdout.println(`Total: ${result}`)","migrationContext":"Python: rebinding with = is always reassignment. Java: type declaration vs assignment. EK9: <- declares, := reassigns — using <- twice creates shadowing.","keywords":["arrow","assign","declare","error","rebind","scope","shadow","variable"],"primaryTopics":["<- vs :=","variable shadowing","declaration"],"typicalErrors":[],"companions":[]}
{"id":882,"category":"Classes and OOP","question":"Why can't I access class fields from outside the class in EK9?","url":"https://ek9.io/qa/QA0882.html","alternatePhrasings":["Are all EK9 class fields private?","How do I expose class state in EK9?","What is the visibility of fields in EK9 classes vs records?"],"answer":"ALL class fields are private. Always. There is no 'public' or 'protected' modifier for fields in EK9's grammar — it does not exist.\n\nTo expose state, write a method:\n  describe()\n    <- rtn as String: `${userName} <${email}>`\n\nRecord fields are the opposite — ALL public. That is the key difference:\n- CLASS: fields private, has methods — for objects with behaviour\n- RECORD: fields public, no methods — for pure data\n\nYou cannot write 'public name as String' on a class. You cannot write a method on a record. The compiler enforces this separation.\n\nTo copy field values, use ':=:' in a constructor:\n  MyClass()\n    -> initialName as String\n    name :=: initialName\n\nSee Q97 for class vs record. See Q95 for field visibility. See Q876 for record basics.","ek9Example":"defines module qa.classes.fields.private\n\n  defines record\n\n    Address\n      street <- String()\n      city <- String()\n\n      Address()\n        ->\n          s as String\n          c as String\n        street :=: s\n        city :=: c\n\n      operator <=> as pure\n        -> arg0 as Address\n        <- rtn as Integer: street <=> arg0.street\n\n      default operator\n\n  defines class\n\n    Person\n      personName <- String()\n      homeAddress <- Address(\"\", \"\")\n\n      Person()\n        ->\n          pName as String\n          addr as Address\n        personName :=: pName\n        homeAddress :=: addr\n\n      //Method exposes state — fields are private\n      location()\n        <- rtn as String: `${personName} lives in ${homeAddress.city}`\n\n      default operator ?\n\n  defines program\n\n    FieldVisibility()\n      stdout <- Stdout()\n\n      //Record fields are public\n      addr <- Address(\"123 Main\", \"Springfield\")\n      stdout.println(addr.city)\n\n      //Class fields accessed via methods\n      person <- Person(\"Steve\", addr)\n      if person?\n        stdout.println(person.location())","migrationContext":"Java: public/private/protected on fields. Python: convention-only _prefix. Kotlin: var/val with getters. EK9: class fields ALWAYS private, record fields ALWAYS public — no modifier needed or allowed.","keywords":["access","class","field","method","private","public","record","visibility"],"primaryTopics":["field visibility","private fields","class vs record"],"typicalErrors":[],"companions":[]}
{"id":883,"category":"Getting Started","question":"How does EK9 handle the concept of 'no value' without null?","url":"https://ek9.io/qa/QA0883.html","alternatePhrasings":["EK9 has no null — so what do I use instead?","What is the tri-state model in EK9?","How do absent, unset, and set work in EK9?"],"answer":"There is no null in EK9. Not deprecated, not hidden — it does not exist in the language.\n\nInstead, every value has three possible states:\n\n1. ABSENT — the variable does not exist (e.g., missing Dict key)\n2. UNSET — the variable exists but has no meaningful data yet\n3. SET — the variable has valid, usable data\n\nYou create UNSET values with the type constructor and no arguments:\n  name <- String()     // exists but unset — name? returns false\n  count <- Integer()   // exists but unset — count? returns false\n\nYou create SET values by providing data:\n  name <- \"Steve\"      // set — name? returns true\n  count <- 42          // set — count? returns true\n\nCollections are different — they are SET even when empty:\n  items <- List() of String   // SET (empty list is a valid list)\n\nAlways check with '?' before using a value that might be unset:\n  if name?\n    stdout.println(name)    // safe\n\nSee Q29 for full tri-state details. See Q84 for Optional type. See Q877 for the ? operator.","ek9Example":"defines module qa.getting.started.tristate\n\n  defines constant\n\n    KNOWN_USER_ID <- 42\n\n  defines function\n\n    findUser() as pure\n      -> userId as Integer\n      <- rtn as String: String()\n\n      //Returns unset String if user not found\n      if userId == KNOWN_USER_ID\n        rtn: \"Steve\"\n\n  defines program\n\n    TriStateDemo()\n      stdout <- Stdout()\n\n      //Set value\n      known <- findUser(KNOWN_USER_ID)\n      if known?\n        stdout.println(`Found: ${known}`)\n\n      //Unset value\n      unknown <- findUser(99)\n      if not unknown?\n        stdout.println(\"User not found — but no null, no exception\")\n\n      //Unset with guard assignment\n      fallback <- String()\n      fallback :=? \"anonymous\"\n      stdout.println(`Fallback: ${fallback}`)","migrationContext":"Java: null + NullPointerException. Python: None. Rust: Option<T>. Kotlin: nullable types. EK9: tri-state (absent/unset/set) with ? operator — NullPointerException is structurally impossible.","keywords":["absent","isset","null","optional","safe","set","tristate","unset"],"primaryTopics":["tri-state","no null","absent/unset/set"],"typicalErrors":[],"companions":[]}
{"id":885,"category":"Streams and Pipelines","question":"Why does EK9 report E06330 when using a function with the wrong type in a stream pipeline?","url":"https://ek9.io/qa/QA0885.html","alternatePhrasings":["What triggers E06330 INCOMPATIBLE_TYPE_ARGUMENTS in streams?","Why must stream function parameter types match the stream element type?","What happens when a uniq function takes the wrong type?"],"answer":"Stream pipeline operations require functions whose parameter types match the current stream element type. When you use `uniq` with a function that accepts a different type, the compiler raises E06330.\n\nTYPE MATCHING IN PIPELINES\nEach pipeline stage knows the current stream type. A function used with uniq must accept the stream element type:\n  cat [1, 2, 3] | uniq hashInteger | ...       // Correct: hashInteger takes Integer\n  cat [1, 2, 3] | uniq hashString | ...        // ERROR: hashString takes String\n\nWHY ENFORCED\nA type mismatch in a pipeline would produce a runtime ClassCastException in Java. EK9 catches this at compile time by validating that each function's parameter type matches the stream type at that stage.\n\nSee Q235 for stream operations reference. See Q237 for streams vs loops. See Q857 for split predicate requirements.","ek9Example":"defines module qa.streams.typemismatch\n\n  defines function\n\n    <?-\n      Hash function for Integer — matches stream of Integer.\n    -?>\n    hashInteger() as pure\n      -> item as Integer\n      <- rtn as Integer: #? item\n\n    <?-\n      Hash function for String — does NOT match stream of Integer.\n      Exists to demonstrate the type mismatch.\n    -?>\n    hashString() as pure\n      -> item as String\n      <- rtn as Integer: #? item\n\n  defines program\n\n    StreamTypeMismatchDemo()\n      stdout <- Stdout()\n\n      collection <- cat [1, 2, 3, 2, 1] | uniq hashInteger | collect as List of Integer\n\n      stdout.println(`Unique count: ${length collection}`)","migrationContext":"Java: Stream type mismatches produce verbose generic errors. Python: map/filter type mismatches caught at runtime only. Go: strict typing on range functions. Rust: iterator adaptor trait bounds checked at compile time. EK9: per-operation type validation with clear error messages.","keywords":["E06330","argument","function","incompatible","mismatch","parameter","pipeline","stream","type","uniq"],"primaryTopics":["stream type mismatch","E06330","INCOMPATIBLE_TYPE_ARGUMENTS"],"typicalErrors":[{"error":"E06330","correct":"uniq hashInteger","incorrect":"uniq hashString","explanation":"The function hashString takes a String parameter, but the stream contains Integer elements. Use a function whose parameter type matches the stream element type. See ek9 -h E06330 for details."}],"companions":[]}
{"id":886,"category":"Constructor Delegation","question":"Why must a default constructor be private when a class has uninitialised fields?","url":"https://ek9.io/qa/QA0886.html","alternatePhrasings":["What triggers E07175 DEFAULT_CONSTRUCTOR_MUST_BE_PRIVATE?","Why does EK9 require private default constructors with uninitialised properties?","How do I prevent creation of objects with uninitialised fields?"],"answer":"When a class has properties that are not initialised at declaration and the developer provides explicit constructors to initialise them, the default no-argument constructor must be made private. Otherwise callers could bypass the developer constructors and create objects with uninitialised properties.\n\nTHE PROBLEM\nWithout `default private`, the no-arg constructor is public:\n  Greeter()          // Creates Greeter with uninitialised 'name'\n  Greeter(\"Steve\")   // Creates Greeter with name = \"Steve\"\nBoth are callable — the first leaves 'name' uninitialised.\n\nTHE FIX\nOption 1: Make default constructor private:\n  default private Greeter()\n\nOption 2: Initialise all fields at declaration:\n  name <- \"\"   // Always initialised, no E07175\n\nSee Q580 for constructor delegation. See Q582 for constructor chaining. See Q584 for abstract constructor patterns.","ek9Example":"defines module qa.constructordelegation.defaultprivate\n\n  defines class\n\n    <?-\n      Greeter with initialised name property.\n      Because name is initialised at declaration, there is\n      no E07175 — the default constructor is safe.\n    -?>\n    Greeter\n      name <- \"\"\n\n      Greeter()\n        -> inName as String\n        name: inName\n\n      greet()\n        <- rtn as String: `Hello ${name}`\n\n      default operator ?\n\n  defines program\n\n    DefaultConstructorPrivateDemo()\n      stdout <- Stdout()\n\n      greeter <- Greeter(\"Steve\")\n      stdout.println(greeter.greet())","migrationContext":"Java: adding any constructor suppresses the default constructor. C#: same as Java. Python: __init__ replaces default. Rust: no default constructor unless Default trait implemented. EK9: default constructor always exists but must be made private when uninitialised fields present.","keywords":["E07175","class","constructor","default","field","initialisation","private","property","safety","uninitialised"],"primaryTopics":["default constructor private","E07175","DEFAULT_CONSTRUCTOR_MUST_BE_PRIVATE"],"typicalErrors":[{"error":"E07175","correct":"name <- \"\"","incorrect":"name as String?","explanation":"When a class has uninitialised properties and developer constructors but no private default constructor, callers can create objects with uninitialised fields. Either initialise all fields at declaration or add 'default private ClassName()'. See ek9 -h E07175 for details."}],"companions":[]}
{"id":887,"category":"Operators and Expressions","question":"Why must the $$ operator always return JSON in EK9?","url":"https://ek9.io/qa/QA0887.html","alternatePhrasings":["What triggers E07580 MUST_RETURN_JSON?","Why does the $$ operator require a JSON return type?","How do I implement the JSON serialisation operator?"],"answer":"The $$ operator is the JSON serialisation operator. It MUST return JSON — returning any other type (String, Integer, etc.) triggers E07580.\n\nOPERATOR CONTRACT\nEach accessor operator has a strict return type:\n  $ — MUST return String (toString equivalent)\n  $$ — MUST return JSON (serialisation)\n  #? — MUST return Integer (hashcode)\n  ? — MUST return Boolean (isSet)\n\nCORRECT PATTERN\n  operator $$ as pure\n    <- rtn as JSON: JSON()\n\nINCORRECT PATTERN\n  operator $$ as pure\n    <- rtn as String: ...   // ERROR: E07580\n\nWHY ENFORCED\nJSON serialisation must produce a JSON value, not a String representation. The `$` operator already handles String conversion. Having `$$` return String would be redundant and semantically wrong.\n\nSee Q238 for operator overview. See Q242 for conversion operators.","ek9Example":"defines module qa.operators.jsonreturn\n\n  defines class\n\n    <?-\n      Configuration class with JSON serialisation.\n    -?>\n    AppConfig\n      appName <- \"MyApp\"\n      appVersion <- \"1.0.0\"\n\n      getName()\n        <- rtn as String: String(appName)\n\n      getVersion()\n        <- rtn as String: String(appVersion)\n\n      operator $ as pure\n        <- rtn as String: `${appName} v${appVersion}`\n\n      operator $$ as pure\n        <- rtn as JSON: JSON()\n\n      override operator ? as pure\n        <- rtn as Boolean: appName?\n\n  defines program\n\n    JsonOperatorReturnDemo()\n      stdout <- Stdout()\n\n      config <- AppConfig()\n      stdout.println(`Config: ${config}`)","migrationContext":"Java: no operator overloading, toString() returns String. Python: __repr__ and __str__ both return str, no JSON operator. Rust: Serialize trait via serde. Go: json.Marshaler returns []byte. EK9: separate $ (String) and $$ (JSON) operators with enforced return types.","keywords":["E07580","JSON","accessor","class","contract","enforce","operator","return","serialisation","type"],"primaryTopics":["JSON operator return type","E07580","MUST_RETURN_JSON"],"typicalErrors":[{"error":"E07580","correct":"      <- rtn as JSON: JSON()","incorrect":"      <- rtn as String: \"{}\"","explanation":"The $$ operator must return JSON, not String. Use `<- rtn as JSON: JSON()` and populate the JSON object. The $ operator handles String conversion. See ek9 -h E07580 for details."}],"companions":[]}
{"id":888,"category":"Syntax and Structure Rules","question":"Why can EK9 programs only return Integer?","url":"https://ek9.io/qa/QA0888.html","alternatePhrasings":["What triggers E07590 PROGRAM_CAN_ONLY_RETURN_INTEGER?","Why can't my program return a String or Boolean?","How do exit codes work in EK9 programs?"],"answer":"EK9 programs can only return Integer (or nothing). The return value becomes the process exit code, which the operating system uses to determine success or failure.\n\nEXIT CODE CONVENTION\n  0 — success\n  1 — general error\n  2 — usage error\n  Non-zero — failure\n\nCORRECT PATTERNS\n  MyProgram()\n    <- exitCode as Integer: 0     // Returns exit code\n\n  MyProgram()\n    ... body ...                   // No return (implicit 0)\n\nINCORRECT PATTERNS\n  MyProgram()\n    <- message as String: \"done\"  // ERROR: E07590\n  MyProgram()\n    <- success as Boolean: true   // ERROR: E07590\n\nWHY ENFORCED\nThe OS can only receive integer exit codes. Returning Float, Boolean, or custom types has no meaning at the OS level. EK9 makes this explicit rather than silently discarding non-integer values.\n\nSee Q871 for hello world. See Q872 for program variables.","ek9Example":"defines module qa.syntaxrules.programreturn\n\n  defines program\n\n    ProgramReturnIntegerDemo()\n      <- exitCode as Integer: 0\n      stdout <- Stdout()\n\n      stdout.println(\"Program running successfully\")\n      stdout.println(`Exit code will be: ${exitCode}`)","migrationContext":"Java: main returns void, exit code via System.exit(int). C: main returns int (exit code). Python: sys.exit(int) or implicit 0. Go: os.Exit(int). Rust: main returns () or Result. EK9: program return type restricted to Integer at compile time.","keywords":["E07590","Integer","code","entry","exit","main","point","process","program","return"],"primaryTopics":["program return type","E07590","PROGRAM_CAN_ONLY_RETURN_INTEGER"],"typicalErrors":[{"error":"E07590","correct":"      <- exitCode as Integer: 0","incorrect":"      <- exitCode as Float: 0.0","explanation":"Programs can only return Integer because the return value is the process exit code. Float, Boolean, String, and custom types cannot be used as exit codes. See ek9 -h E07590 for details."}],"companions":[]}
{"id":889,"category":"Syntax and Structure Rules","question":"What arguments can an EK9 program accept?","url":"https://ek9.io/qa/QA0889.html","alternatePhrasings":["What triggers E07610 PROGRAM_ARGUMENTS_INAPPROPRIATE?","Why can't I mix List of String with typed parameters?","How do EK9 program argument styles work?"],"answer":"EK9 programs support two argument styles, but you cannot mix them in a single program.\n\nSTYLE 1: C-STYLE LIST\n  MyProgram()\n    -> argv as List of String\n    // Parse arguments manually\n\nSTYLE 2: TYPED PARAMETERS\n  MyProgram()\n    ->\n      name as String\n      count as Integer\n    // EK9 parses from command-line strings automatically\n\nCANNOT MIX\n  MyProgram()\n    ->\n      count as Integer\n      argv as List of String    // ERROR: E07610\nMixing typed parameters with List of String is ambiguous.\n\nALLOWED TYPED PARAMETERS\nOnly types that can be parsed from command-line strings: String, Integer, Float, Boolean, Date, DateTime, Duration, Millisecond, Colour, Dimension, Money, Path, RegularExpression.\n\nSee Q871 for hello world. See Q888 for program return type.","ek9Example":"defines module qa.syntaxrules.programarguments\n\n  defines program\n\n    <?-\n      Program using typed arguments.\n      EK9 automatically parses command-line strings to the declared types.\n    -?>\n    ProgramArgumentsDemo()\n      ->\n        userName as String\n        repeatCount as Integer\n\n      stdout <- Stdout()\n\n      for i in 1 ... repeatCount\n        stdout.println(`${i}: Hello, ${userName}`)","migrationContext":"Java: String[] only, all parsing manual. Python: sys.argv is list of strings, argparse for typed. Go: os.Args is []string, flag for typed. Rust: std::env::args(), clap for typed. EK9: two styles — C-style List of String or compiler-parsed typed parameters, but not both.","keywords":["E07610","List","String","argument","command","line","mix","parameter","program","typed"],"primaryTopics":["program argument styles","E07610","PROGRAM_ARGUMENTS_INAPPROPRIATE"],"typicalErrors":[{"error":"E07610","correct":"      userName as String","incorrect":"      argv as List of String","explanation":"You cannot mix typed parameters with List of String in a program. Choose one style: either a single List of String parameter, or typed parameters that EK9 parses automatically. See ek9 -h E07610 for details."}],"companions":[]}
{"id":890,"category":"Web Services","question":"Why must service operations return HTTPResponse in EK9?","url":"https://ek9.io/qa/QA0890.html","alternatePhrasings":["What triggers E07750 SERVICE_INCOMPATIBLE_RETURN_TYPE?","Why can't my service method return String or Boolean?","What return type do EK9 service methods require?"],"answer":"Every service method must return HTTPResponse. Returning Boolean, String, Integer, or any other type triggers E07750.\n\nWHY HTTPRESPONSE\nHTTP responses carry status codes, headers, content type, and body. Only HTTPResponse expresses all of these:\n  status() — 200, 404, 500, etc.\n  content() — response body\n  contentType() — MIME type\n  cacheControl() — caching directives\n  contentLanguage() — response language\n\nCORRECT PATTERN\n  healthCheck() as GET for :/status\n    <- response as HTTPResponse: ...\n\nINCORRECT PATTERN\n  healthCheck() as GET for :/status\n    <- response <- true            // ERROR: E07750\n\nSee Q659 for HTTPResponse details. See Q657 for URI mapping. See Q660 for CRUD patterns.","ek9Example":"defines module qa.webdeep.servicereturntype\n\n  defines service\n\n    <?-\n      API service with correct HTTPResponse return types.\n    -?>\n    StatusApi :/api open\n\n      healthCheck() as GET for :/health\n        <- response as HTTPResponse: () with trait of HTTPResponse\n          override content()\n            <- rtn as String: `{\"status\": \"healthy\"}`\n          override status() as pure\n            <- rtn as Integer: 200\n          override contentType() as pure\n            <- rtn as String: \"application/json\"\n          override cacheControl() as pure\n            <- rtn as String: \"no-cache\"\n          override contentLanguage() as pure\n            <- rtn as String: \"en\"\n          default operator ?\n\n  defines application\n\n    StatusApp\n      register StatusApi()\n\n  defines program\n\n    ServiceReturnTypeDemo()\n      stdout <- Stdout()\n      stdout.println(\"Service methods must return HTTPResponse\")","migrationContext":"Java: Spring returns any type or ResponseEntity. Python: Flask returns (body, status). Go: writes to http.ResponseWriter. Rust: HttpResponse value. EK9: mandates HTTPResponse trait, explicit status/content/type.","keywords":["E07750","GET","HTTPResponse","POST","content","incompatible","return","service","status","web"],"primaryTopics":["service return type","E07750","SERVICE_INCOMPATIBLE_RETURN_TYPE"],"typicalErrors":[{"error":"E07750","correct":"as HTTPResponse","incorrect":"as Boolean","explanation":"Service methods must return HTTPResponse, not Boolean. The HTTP protocol requires status codes, headers, and content type, which only HTTPResponse can express. See ek9 -h E07750 for details."}],"companions":[]}
{"id":891,"category":"Streams and Pipelines","question":"Why must I sort before grouping in an EK9 stream pipeline?","url":"https://ek9.io/qa/QA0891.html","alternatePhrasings":["What triggers E10040 SORT_REQUIRED_BEFORE_GROUP?","Why does group need a preceding sort in streams?","How does change-detection grouping work in EK9?"],"answer":"EK9 stream group uses change-detection: it creates a new group when the hashcode changes between adjacent items. Without sorted input, items with the same key scattered throughout the stream produce multiple incomplete groups.\n\nCORRECT PATTERN\n  cat items | sort | group > collection\nSorting ensures identical items are adjacent, so group detects each run correctly.\n\nINCORRECT PATTERN\n  cat items | group > collection        // ERROR: E10040\nWithout sort, group may split identical items into separate groups.\n\nTYPE-CHANGING INVALIDATES SORT\n  cat items | sort | map with transform | group > collection  // ERROR: E10040\nThe map changes the stream type, making the preceding sort irrelevant. Add another sort after the map.\n\nFILTER PRESERVES SORT\n  cat items | sort | filter by predicate | group > collection  // OK\nFilter removes items without changing type, so sort remains valid.\n\nSee Q235 for stream operations. See Q237 for streams vs loops.","ek9Example":"defines module qa.streams.sortbeforegroup\n\n  defines program\n\n    SortBeforeGroupDemo()\n      stdout <- Stdout()\n\n      grouped <- List() of List of String\n      cat [\"A\", \"B\", \"A\", \"C\", \"B\"] | sort | group > grouped\n\n      stdout.println(`Groups found: ${length grouped}`)","migrationContext":"Java: Collectors.groupingBy uses HashMap, order irrelevant. Python: itertools.groupby requires sorted input but no compile-time check. Go: no built-in group. Rust: group_by requires sorted, no compile check. EK9: compiler enforces sort before group at compile time.","keywords":["E10040","adjacent","change","collect","detection","group","order","pipeline","sort","stream"],"primaryTopics":["sort before group","E10040","SORT_REQUIRED_BEFORE_GROUP"],"typicalErrors":[{"error":"E10040","correct":"      cat [\"A\", \"B\", \"A\", \"C\", \"B\"] | sort | group > grouped","incorrect":"      cat [\"A\", \"B\", \"A\", \"C\", \"B\"] | group > grouped","explanation":"Group uses change-detection on adjacent items. Without sort, identical items scattered in the stream produce incomplete groups. Add sort before group. See ek9 -h E10040 for details."}],"companions":[]}
{"id":892,"category":"DI Validation","question":"What is the maximum number of DI registrations in an EK9 application block?","url":"https://ek9.io/qa/QA0892.html","alternatePhrasings":["What triggers E11042 EXCESSIVE_APPLICATION_REGISTRATIONS?","How many register statements can an application have?","Why does EK9 limit application registrations to twelve?"],"answer":"An application block can have at most 12 registrations. More than 12 triggers E11042, indicating the application is trying to be a monolith.\n\nWHY TWELVE\nResearch in component-based software engineering shows that systems with focused, modular composition are more maintainable. A large registration block often means the application mixes unrelated concerns.\n\nSPLIT INTO SUB-APPLICATIONS\n  defines application\n    InfrastructureApp\n      register DbService() as AbstractDb\n      register CacheService() as AbstractCache\n\n  defines application\n    BusinessApp with application of InfrastructureApp\n      register UserService() as AbstractUser\n      register OrderService() as AbstractOrder\n\nUSE COMPOSITION\nThe 'with application of' clause composes smaller applications. Each sub-application has focused responsibilities, and the combined application inherits all registrations.\n\nSee Q667 for abstract injection. See Q798 for missing/duplicate registrations. See Q324 for DI basics.","ek9Example":"defines module qa.divalidation.applicationregistrations\n\n  defines component\n\n    Api01 as abstract\n      work01() as abstract\n        <- rtn as String?\n      default operator ?\n    Svc01 extends Api01\n      override work01()\n        <- rtn <- \"s01\"\n      default operator ?\n\n    Api02 as abstract\n      work02() as abstract\n        <- rtn as String?\n      default operator ?\n    Svc02 extends Api02\n      override work02()\n        <- rtn <- \"s02\"\n      default operator ?\n\n    Api03 as abstract\n      work03() as abstract\n        <- rtn as String?\n      default operator ?\n    Svc03 extends Api03\n      override work03()\n        <- rtn <- \"s03\"\n      default operator ?\n\n    Api04 as abstract\n      work04() as abstract\n        <- rtn as String?\n      default operator ?\n    Svc04 extends Api04\n      override work04()\n        <- rtn <- \"s04\"\n      default operator ?\n\n    Api05 as abstract\n      work05() as abstract\n        <- rtn as String?\n      default operator ?\n    Svc05 extends Api05\n      override work05()\n        <- rtn <- \"s05\"\n      default operator ?\n\n    Api06 as abstract\n      work06() as abstract\n        <- rtn as String?\n      default operator ?\n    Svc06 extends Api06\n      override work06()\n        <- rtn <- \"s06\"\n      default operator ?\n\n    Api07 as abstract\n      work07() as abstract\n        <- rtn as String?\n      default operator ?\n    Svc07 extends Api07\n      override work07()\n        <- rtn <- \"s07\"\n      default operator ?\n\n    Api08 as abstract\n      work08() as abstract\n        <- rtn as String?\n      default operator ?\n    Svc08 extends Api08\n      override work08()\n        <- rtn <- \"s08\"\n      default operator ?\n\n    Api09 as abstract\n      work09() as abstract\n        <- rtn as String?\n      default operator ?\n    Svc09 extends Api09\n      override work09()\n        <- rtn <- \"s09\"\n      default operator ?\n\n    Api10 as abstract\n      work10() as abstract\n        <- rtn as String?\n      default operator ?\n    Svc10 extends Api10\n      override work10()\n        <- rtn <- \"s10\"\n      default operator ?\n\n    Api11 as abstract\n      work11() as abstract\n        <- rtn as String?\n      default operator ?\n    Svc11 extends Api11\n      override work11()\n        <- rtn <- \"s11\"\n      default operator ?\n\n    Api12 as abstract\n      work12() as abstract\n        <- rtn as String?\n      default operator ?\n    Svc12 extends Api12\n      override work12()\n        <- rtn <- \"s12\"\n      default operator ?\n\n    Api13 as abstract\n      work13() as abstract\n        <- rtn as String?\n      default operator ?\n    Svc13 extends Api13\n      override work13()\n        <- rtn <- \"s13\"\n      default operator ?\n\n  defines application\n\n    <?-\n      Application with exactly 12 registrations. At the limit but valid.\n      Adding a 13th would trigger E11042.\n    -?>\n    FocusedApp\n      register Svc01() as Api01\n      register Svc02() as Api02\n      register Svc03() as Api03\n      register Svc04() as Api04\n      register Svc05() as Api05\n      register Svc06() as Api06\n      register Svc07() as Api07\n      register Svc08() as Api08\n      register Svc09() as Api09\n      register Svc10() as Api10\n      register Svc11() as Api11\n      register Svc12() as Api12\n\n  defines program\n\n    ApplicationRegistrationsDemo() with application of FocusedApp\n      stdout <- Stdout()\n      svc01 as Api01!\n      result <- svc01.work01()\n      stdout.println(`Result: ${result}`)","migrationContext":"Java: Spring has no limit on beans per context. Python: no DI framework limit. Go: no DI framework limit. Rust: no standard DI. EK9: compiler enforces 12-registration limit per application block, encouraging modular composition.","keywords":["DI","E11042","application","composition","limit","modular","monolith","registration","split","twelve"],"primaryTopics":["application registration limit","E11042","EXCESSIVE_APPLICATION_REGISTRATIONS"],"typicalErrors":[{"error":"E11042","correct":"      register Svc12() as Api12","incorrect":"      register Svc12() as Api12\n      register Svc13() as Api13","explanation":"An application block can have at most 12 registrations. This mutation adds a 13th, triggering E11042. Split large applications into focused sub-applications. See ek9 -h E11042 for details."}],"companions":[]}
{"id":893,"category":"Syntax and Structure Rules","question":"Why does EK9 require 'defines class' and 'defines function' section headers?","url":"https://ek9.io/qa/QA0893.html","alternatePhrasings":["What are EK9 section headers and why are they mandatory?","Why can't I just write a class without 'defines class'?","What is the 'defines' keyword in EK9?","How do EK9 section headers work?"],"answer":"EK9 uses mandatory section headers to organize code into typed blocks. This works like Pascal's type/var/procedure sections — every construct MUST appear under its appropriate 'defines' header.\n\nLike Pascal's 'type' section where you declare types, EK9's 'defines class' section is where you declare classes. Like Pascal's 'procedure' section, EK9's 'defines function' section is where you declare functions.\n\nMANDATORY SECTION HEADERS\nEK9 requires the following section headers:\n- defines module — declares the module (package), MUST be first\n- defines class — contains class definitions\n- defines record — contains record (value type) definitions\n- defines function — contains standalone function definitions\n- defines trait — contains trait (interface) definitions\n- defines program — contains executable entry points\n- defines component — contains DI-injectable components\n- defines type — contains constrained types and enumerations\n- defines constant — contains module-level constants\n- defines service — contains HTTP service definitions\n- defines application — contains DI wiring\n\nWITHOUT SECTION HEADERS = PARSE FAILURE\nWriting a class without 'defines class' above it is a grammar error — the parser rejects it immediately, before any compilation even starts.\n\nWHY THIS DESIGN\n- Like Pascal's structured declarations, sections make code self-documenting\n- The compiler knows what kind of construct to expect\n- AI assistants can generate correct section placement\n- Modules can have multiple 'defines class' sections for grouping\n\nMULTIPLE SECTIONS\nYou can have multiple sections of the same type in one module — useful for grouping related types.\n\nSee Q93 for class definitions. See Q49 for function definitions. See Q97 for records vs classes.","ek9Example":"defines module qa.syntax.sectionheaders\n\n  defines constant\n\n    DEFAULT_GREETING <- \"Hello\"\n\n  defines record\n\n    <?-\n      Records go under 'defines record' section.\n      Like Pascal's record type declaration under 'type'.\n    -?>\n    Coordinate\n      xPos as Float: 0.0\n      yPos as Float: 0.0\n\n      Coordinate()\n        ->\n          xPos as Float\n          yPos as Float\n        this.xPos :=: xPos\n        this.yPos :=: yPos\n\n      default operator\n\n  defines function\n\n    <?-\n      Functions go under 'defines function' section.\n      Like Pascal's 'procedure'/'function' section.\n    -?>\n    calculateDistance() as pure\n      ->\n        pointA as Coordinate\n        pointB as Coordinate\n      <-\n        rtn as Float: 0.0\n\n      diffX <- pointA.xPos - pointB.xPos\n      diffY <- pointA.yPos - pointB.yPos\n      rtn := sqrt(diffX * diffX + diffY * diffY)\n\n  defines trait\n\n    <?-\n      Traits go under 'defines trait' section.\n      No braces, no semicolons — just indentation.\n    -?>\n    Describable\n      describe() as pure abstract\n        <- rtn as String?\n\n  defines class\n\n    <?-\n      Classes go under 'defines class' section.\n      Like Pascal's type section for class declarations.\n    -?>\n    Circle\n      origin as Coordinate: Coordinate()\n      radius as Float: 1.0\n\n      Circle()\n        ->\n          origin as Coordinate\n          radius as Float\n        this.origin :=: origin\n        this.radius :=: radius\n\n      area() as pure\n        <- rtn as Float: 3.14159 * radius * radius\n\n      default operator\n\n  defines program\n\n    SectionHeadersDemo()\n      stdout <- Stdout()\n\n      origin <- Coordinate()\n      target <- Coordinate(3.0, 4.0)\n      distance <- calculateDistance(origin, target)\n      stdout.println(`Distance: ${distance}`)\n\n      circle <- Circle(origin, 5.0)\n      stdout.println(`Circle area: ${circle.area()}`)","migrationContext":"Pascal: uses type/var/procedure/function section headers — EK9 follows this pattern with 'defines class/function/record'. Java: no section headers, class/interface at top level. Python: no section headers, class/def anywhere. Go: type/func/var at top level, no grouping required. Kotlin: class/fun at top level. EK9: mandatory 'defines class/function/record/program' section headers organize code — think Pascal's structured declarations.","keywords":["Pascal","class","defines","function","header","mandatory","module","program","record","section","syntax","trait"],"primaryTopics":["section headers","defines keyword","code structure"],"typicalErrors":[{"error":"E01010","correct":"  defines class\n\n    <?-\n      Classes go under 'defines class' section.","incorrect":"  class Person\n    name <- String()","explanation":"EK9 requires 'defines class' section header before any class definition. Without it, the parser cannot determine the construct type. Like Pascal requires 'type' before type declarations. See ek9 -h E01000 for details."},{"error":"E01010","correct":"  defines function\n\n    <?-\n      Functions go under 'defines function' section.","incorrect":"  function greet()\n    <- rtn as String: \"hello\"","explanation":"EK9 requires 'defines function' section header before function definitions. The 'function' keyword alone is not valid syntax. Like Pascal's 'procedure' section. See ek9 -h E01000 for details."}],"companions":[]}
{"id":894,"category":"Syntax and Structure Rules","question":"How do EK9 constructors work with parameters?","url":"https://ek9.io/qa/QA0894.html","alternatePhrasings":["What is the constructor parameter syntax in EK9?","How do I pass parameters to an EK9 constructor?","Why does my EK9 constructor fail to parse?","How do multi-parameter constructors work in EK9?"],"answer":"EK9 constructor parameters use the '->' data-in port syntax on a SEPARATE LINE after the constructor name — like Go's named return values, the parameters are declared on their own indented block.\n\nBASIC CONSTRUCTOR\nThe constructor shares the class name. Parameters use '->' on a separate line:\n  MyClass()\n    -> paramName as Type\n    this.field :=: paramName\n\nThe '->' means 'data coming IN' (like Go's named return parameters or Ada's 'in' port). The '<-' means 'data going OUT'.\n\nMULTI-PARAMETER CONSTRUCTOR\nMultiple parameters each get their own line under '->':\n  MyClass()\n    ->\n      firstName as String\n      lastName as String\n      birthYear as Integer\n    this.firstName :=: firstName\n    this.lastName :=: lastName\n    this.birthYear :=: birthYear\n\nBODY USES :=: FOR COPY ASSIGNMENT\nInside the constructor body, use ':=:' (copy operator) to copy parameter values into fields. This creates a COPY, ensuring the class owns its data.\n\nSINGLE-LINE vs MULTI-LINE\nSingle parameter: -> on same line as parameter\nMultiple parameters: -> on its own line, then parameters indented below\n\nCOMMON MISTAKES\n- Putting -> and parameters on the class name line (parse error)\n- Using := instead of :=: for field assignment in constructor body\n- Forgetting the () after the class name in the constructor\n\nSee Q93 for class definitions. See Q95 for field visibility. See Q881 for declaration vs assignment.","ek9Example":"defines module qa.syntax.constructors\n\n  defines class\n\n    <?-\n      Single-parameter constructor.\n      Like Go: func NewPerson(name string) *Person\n      EK9: -> on separate line for data-in.\n    -?>\n    Greeting\n      message as String: String()\n\n      Greeting()\n        -> message as String\n        this.message :=: message\n\n      getMessage() as pure\n        <- rtn as String: message\n\n      default operator\n\n    <?-\n      Multi-parameter constructor.\n      Like Go named return params, each on own line.\n      Body uses :=: (copy) to assign fields.\n    -?>\n    Employee\n      employeeName as String: String()\n      department as String: String()\n      yearJoined as Integer: 0\n\n      Employee()\n        ->\n          employeeName as String\n          department as String\n          yearJoined as Integer\n        this.employeeName :=: employeeName\n        this.department :=: department\n        this.yearJoined :=: yearJoined\n\n      describe() as pure\n        <- rtn as String: `${employeeName} in ${department} since ${yearJoined}`\n\n      default operator\n\n    <?-\n      Constructor with default values via multiple constructors.\n      Like Go's functional options pattern.\n    -?>\n    Configuration\n      hostAddress as String: \"localhost\"\n      portNumber as Integer: 8080\n      isSecure as Boolean: false\n\n      Configuration()\n        ->\n          hostAddress as String\n          portNumber as Integer\n          isSecure as Boolean\n        this.hostAddress :=: hostAddress\n        this.portNumber :=: portNumber\n        this.isSecure :=: isSecure\n\n      Configuration()\n        ->\n          hostAddress as String\n          portNumber as Integer\n        this.hostAddress :=: hostAddress\n        this.portNumber :=: portNumber\n\n      describe() as pure\n        <- rtn as String: `${hostAddress}:${portNumber} secure=${isSecure}`\n\n      default operator\n\n  defines program\n\n    ConstructorDemo()\n      stdout <- Stdout()\n\n      hello <- Greeting(\"Welcome to EK9\")\n      stdout.println(hello.getMessage())\n\n      engineer <- Employee(\"Alice\", \"Engineering\", 2023)\n      stdout.println(engineer.describe())\n\n      defaultConfig <- Configuration(\"api.example.com\", 443, true)\n      stdout.println(defaultConfig.describe())\n\n      simpleConfig <- Configuration(\"localhost\", 3000)\n      stdout.println(simpleConfig.describe())","migrationContext":"Go: func NewPerson(name string, age int) *Person — named returns on separate lines. Java: public MyClass(String name, int age) { this.name = name; } with braces. Python: def __init__(self, name, age): self.name = name. Kotlin: class MyClass(val name: String, val age: Int) — primary constructor inline. EK9: MyClass() with -> parameters on separate lines, body uses :=: copy operator — think Go's named parameters on separate lines.","keywords":["Go","arrow","assign","class","constructor","copy","data-in","multi-parameter","parameter","syntax"],"primaryTopics":["constructor syntax","parameter passing","copy assignment"],"typicalErrors":[{"error":"E01010","correct":"      Employee()\n        ->\n          employeeName as String\n          department as String\n          yearJoined as Integer\n        this.employeeName :=: employeeName","incorrect":"    Employee(employeeName as String, department as String)\n      this.employeeName :=: employeeName\n      this.department :=: department","explanation":"EK9 constructors use -> on a SEPARATE line for parameters, not inline parentheses like Java. The -> means 'data coming in' like Go's named parameters. See ek9 -h E01000 for details."}],"companions":[]}
{"id":895,"category":"Getting Started","question":"What is the difference between <- and := in EK9?","url":"https://ek9.io/qa/QA0895.html","alternatePhrasings":["When do I use <- vs := in EK9?","Why does EK9 have two assignment operators?","What is the declaration operator in EK9?","How do I reassign a variable in EK9?"],"answer":"EK9 has THREE distinct assignment operators, each with precise semantics. Getting these wrong is one of the most common mistakes.\n\nTHREE OPERATORS\n<- is DECLARATION: creates a NEW variable with its first value.\n:= is ASSIGNMENT: assigns to an EXISTING variable.\n:=? is GUARDED ASSIGNMENT: only assigns if the variable is currently UNSET.\n\nRULE: Use <- exactly ONCE per variable (when you create it). Use := for all subsequent assignments.\n\nEXAMPLES\n  counter <- 0         declaration, creates counter\n  counter := counter + 1   reassignment, counter already exists\n  counter := 10        reassignment again\n\nCOMMON MISTAKE\nUsing <- when you mean to reassign:\n  score <- 100         declaration — OK\n  score <- 200         ERROR — score already exists, use := instead\n\nUsing := when the variable doesn't exist yet:\n  total := 0           ERROR — total not declared yet, use <- instead\n\nGUARDED ASSIGNMENT :=?\nOnly assigns if the variable is currently unset:\n  nickname <- String()     declared but unset\n  nickname :=? \"Default\"   assigns because nickname is unset\n  nickname :=? \"Other\"     does NOTHING because nickname is now set\n\nIN FUNCTIONS\n<- declares the return variable:\n  myFunction()\n    -> inputValue as String\n    <- outputResult as String: inputValue\nThe <- creates outputResult as the return. Any updates to it use :=\n\nSee Q22 for variable declarations. See Q93 for class definitions. See Q881 for more assignment examples.","ek9Example":"defines module qa.gettingstarted.declarevsassign\n\n  defines function\n\n    <?-\n      Shows <- for declaration and := for reassignment.\n      EK9 separates these into distinct operators.\n    -?>\n    countPositives() as pure\n      -> numbers as List of Integer\n      <- positiveCount as Integer: 0\n\n      for n in numbers\n        if n > 0\n          positiveCount := positiveCount + 1\n\n    <?-\n      Shows :=? guarded assignment.\n      Like Kotlin's ?: elvis operator.\n    -?>\n    firstNonEmpty() as pure\n      ->\n        primary as String\n        fallback as String\n      <-\n        chosen as String: String()\n\n      chosen :=? primary\n      chosen :=? fallback\n\n    <?-\n      Shows all three operators in one function.\n    -?>\n    buildGreeting() as pure\n      ->\n        prefix as String\n        userName as String\n      <-\n        greeting as String: String()\n\n      greeting :=? prefix\n      greeting :=? \"Hello\"\n\n      separator <- \", \"\n      greeting := `${greeting}${separator}${userName}`\n\n  defines program\n\n    DeclarationVsAssignmentDemo()\n      stdout <- Stdout()\n\n      // <- DECLARATION: creates the variable\n      score <- 100\n      stdout.println(`Initial score: ${score}`)\n\n      // := ASSIGNMENT: updates existing variable\n      score := score + 50\n      stdout.println(`Updated score: ${score}`)\n\n      score := score - 20\n      stdout.println(`Final score: ${score}`)\n\n      // :=? GUARDED ASSIGNMENT: only if unset\n      nickname <- String()\n      nickname :=? \"DefaultUser\"\n      stdout.println(`Nickname: ${nickname}`)\n\n      nickname :=? \"WontChange\"\n      stdout.println(`Still: ${nickname}`)\n\n      // Function examples\n      positives <- countPositives([3, -1, 7, -2, 5])\n      stdout.println(`Positives: ${positives}`)\n\n      chosen <- firstNonEmpty(String(), \"fallback\")\n      stdout.println(`Chosen: ${chosen}`)\n\n      msg <- buildGreeting(\"Hi\", \"Steve\")\n      stdout.println(msg)","migrationContext":"Go separates declaration from reassignment with different operators — EK9 does the same with <- and :=. Python and Java use = for both (ambiguous). Kotlin uses val/var keywords. Rust uses let/let mut. EK9 uses <- to declare, := to reassign, :=? for guarded assignment.","keywords":["assignment","coalesce","create","declaration","declare","guard","operator","reassign","variable"],"primaryTopics":["declaration operator","assignment operator","guarded assignment"],"typicalErrors":[{"error":"E50050","correct":"score := score + 50","incorrect":"score <- score + 50","explanation":"Use ':=' to reassign an existing variable; re-using '<-' (declaration only) on the already-declared 'score' duplicates the variable. See ek9 -h E50050 for details."}],"companions":[]}
{"id":896,"category":"Streams and Pipelines","question":"How do I output stream results to stdout in EK9?","url":"https://ek9.io/qa/QA0896.html","alternatePhrasings":["How do EK9 stream pipelines output data?","What is the > operator in EK9 streams?","How do I redirect stream output in EK9?","How do streams print to console in EK9?"],"answer":"EK9 stream pipelines use '>' for output redirection — just like Unix shell pipes. The '>' sends stream results to a target like stdout.\n\nBASIC PATTERN\nLike Unix: ls | grep foo > output.txt\nEK9:  cat items | filter by predicate > stdout\n\nThe '>' is the stream terminal — it sends each item to the target.\n\nSTREAM TO STDOUT\n  cat items | filter by isPositive > stdout\nThis is like Unix: cat file | grep pattern > /dev/stdout\n\nSTREAM TO COLLECTION\n  filtered <- cat items | filter by isPositive | collect as List of Integer\nThe 'collect' terminal gathers items into a new collection.\n\nSTREAM PIPELINE OPERATORS\n- cat: source items (like Unix cat)\n- filter: keep items matching predicate (like Unix grep)\n- map: transform items (like Unix awk/sed)\n- sort: order items\n- head: take first N items (like Unix head)\n- tail: take last N items (like Unix tail)\n- skip: skip first N items\n- tee: fork output (like Unix tee)\n- group: group items (like Unix uniq)\n- join: merge groups\n- flatten: expand nested structures\n- > : redirect output (like Unix >)\n- collect: gather into collection\n\nCOMMON MISTAKE\nDo NOT use <- to assign stream output directly:\n  stdout <- cat items   WRONG — this declares stdout\n  cat items > stdout     CORRECT — this redirects output\n\nSee Q140 for stream basics. See Q141 for filter and map.","ek9Example":"defines module qa.streams.redirect\n\n  defines function\n\n    isPositive() as pure\n      -> candidate as Integer\n      <- rtn as Boolean: candidate > 0\n\n    doubleIt() as pure\n      -> candidate as Integer\n      <- rtn as Integer: candidate * 2\n\n  defines program\n\n    StreamOutputDemo()\n      stdout <- Stdout()\n\n      numbers <- [5, -3, 8, -1, 12, -7, 3]\n\n      // Like Unix: cat numbers | grep positive > stdout\n      stdout.println(\"Positive numbers:\")\n      cat numbers | filter by isPositive > stdout\n\n      // Like Unix: cat numbers | grep positive | awk '{print $1*2}' > stdout\n      stdout.println(\"Doubled positives:\")\n      cat numbers | filter by isPositive | map by doubleIt > stdout\n\n      // Like Unix: cat numbers | sort > stdout\n      stdout.println(\"Sorted:\")\n      cat numbers | sort > stdout\n\n      // Like Unix: cat numbers | head -3 > stdout\n      stdout.println(\"First 3:\")\n      cat numbers | head 3 > stdout\n\n      // Tail — like Unix: cat numbers | tail -3 > stdout\n      stdout.println(\"Last 3:\")\n      cat numbers | tail 3 > stdout","migrationContext":"Unix: ls | grep foo | sort > output.txt — EK9 streams follow the same model. Java: stream().filter().map().forEach() — method chaining instead of pipes. Python: list comprehensions or generator expressions. Rust: iter().filter().map().collect() — similar to Java. EK9: cat items | filter by predicate | sort > stdout — Unix pipe syntax with typed pipeline stages.","keywords":["Unix","cat","collect","filter","map","output","pipe","pipeline","redirect","stdout","stream"],"primaryTopics":["stream output","redirect operator","Unix pipes"],"typicalErrors":[],"companions":[]}
{"id":897,"category":"Operators and Expressions","question":"What do the $ and #? operators mean in EK9?","url":"https://ek9.io/qa/QA0897.html","alternatePhrasings":["What is the $ operator in EK9?","What is the #? operator in EK9?","How does string conversion work in EK9?","How does hashcode work in EK9?"],"answer":"EK9 has dedicated operator symbols for string conversion and hashcode. These are NOT what you might expect from other languages.\n\n$ OPERATOR — STRING CONVERSION (NOT interpolation)\nThe $ operator calls the _string() method to convert any value to its String representation:\n  age <- 25\n  ageText <- $age         converts Integer 25 to String \"25\"\n  point <- Point(3.0, 4.0)\n  pointText <- $point     calls point._string()\n\nIMPORTANT: $ is NOT string interpolation. For interpolation, EK9 uses backtick strings with ${...}:\n  greeting <- `Hello ${name}`   interpolation inside backtick string\n  nameStr <- $name               standalone string conversion\n\nInside backtick strings, ${$variable} uses $ to convert to string:\n  stdout.println(`Age is ${$age}`)   ${...} is the interpolation, $ inside converts\n\n#? OPERATOR — HASHCODE (lowercase 'c')\nThe #? operator calls _hashcode() and returns an Integer hash value:\n  hashValue <- #? myObject    calls myObject._hashcode()\n\nNote: it is _hashcode (lowercase c), not _hashCode (Java style).\n\n#^ OPERATOR — PROMOTE (type conversion)\nThe #^ operator calls _promote() to convert to a wider type:\n  anInteger <- 42\n  asFloat <- #^ anInteger   promotes Integer to Float\n\nIMPORTANT: #^ is NOT Python list comprehension. It is type PROMOTION only.\n\nOPERATOR METHOD NAMES\n- $ calls _string()\n- #? calls _hashcode()\n- #^ calls _promote()\n- ? calls _isSet()\n- <=> calls _cmp()\n- == calls _eq()\n- <> calls _neq()\n\nSee Q96 for all operators. See Q238 for operator enforcement. See Q245 for implementing operators.","ek9Example":"defines module qa.operators.stringhash\n\n  defines record\n\n    Coordinate\n      xPos as Float: 0.0\n      yPos as Float: 0.0\n\n      Coordinate()\n        ->\n          initialX as Float\n          initialY as Float\n        this.xPos :=: initialX\n        this.yPos :=: initialY\n\n      default operator\n\n  defines function\n\n    showConversions() as pure\n      ->\n        label as String\n        coordinate as Coordinate\n      <-\n        rtn as String: String()\n\n      // $ operator converts to String via _string()\n      coordText <- $coordinate\n      hashVal <- #? coordinate\n      rtn := `${label}: text=${coordText}, hash=${hashVal}`\n\n  defines program\n\n    StringAndHashDemo()\n      stdout <- Stdout()\n\n      // $ operator — string CONVERSION (not interpolation)\n      age <- 25\n      ageText <- $age\n      stdout.println(`Age as string: ${ageText}`)\n\n      // Inside backtick strings: ${variable} for conversion\n      price <- 19.99\n      stdout.println(`Price: ${price}`)\n\n      // #? operator — hashcode\n      greeting <- \"Hello\"\n      greetingHash <- #? greeting\n      stdout.println(`Hash of greeting: ${greetingHash}`)\n\n      // #^ operator — promote (type conversion)\n      wholeNumber <- 42\n      promoted <- #^ wholeNumber\n      stdout.println(`Promoted: ${promoted}`)\n\n      // Custom type with operators\n      origin <- Coordinate()\n      target <- Coordinate(3.0, 4.0)\n      stdout.println(showConversions(\"Origin\", origin))\n      stdout.println(showConversions(\"Target\", target))","migrationContext":"Java: toString() and hashCode() are method calls. Python: str() and hash() are built-in functions, $ has no meaning. Rust: Display trait for string, Hash trait for hashing. Go: String() method convention. Kotlin: toString() and hashCode() like Java. EK9: $ for _string() conversion, #? for _hashcode(), #^ for _promote() — these are operator symbols, not method calls.","keywords":["backtick","conversion","dollar","hashcode","interpolation","operator","promote","string"],"primaryTopics":["$ operator","#? operator","#^ operator","string conversion"],"typicalErrors":[{"error":"E07620","correct":"ageText <- $age","incorrect":"ageText <- \"Age: \" + age","explanation":"Use $ to convert an Integer to String before concatenation — \"Age: \" + age has no defined + operator for a String and an Integer. See ek9 -h E07620 for details."}],"companions":[]}
{"id":898,"category":"Operators and Expressions","question":"What does the ? operator mean in EK9?","url":"https://ek9.io/qa/QA0898.html","alternatePhrasings":["How does isSet work in EK9?","What does the question mark operator do in EK9?","How do I check if a variable is set in EK9?","What does appending ? after a variable do in EK9?"],"answer":"In EK9, the ? operator checks if a value is SET. It is a SUFFIX operator — append it after the variable name.\n\nWHAT ? DOES\nThe ? operator goes AFTER the variable:\n  name <- String()       declared but UNSET\n  if name?               false — name has no value yet\n    stdout.println(name) skipped\n\n  name := \"Alice\"        now SET\n  if name?               true — name has a value\n    stdout.println(name) prints \"Alice\"\n\nThe ? operator calls _isSet() internally and returns Boolean.\n\nTRI-STATE MODEL\nEK9 objects have three states:\n1. ABSENT — object doesn't exist (Optional empty, Dict missing key)\n2. PRESENT but UNSET — object exists but has no meaningful value\n3. PRESENT and SET — object exists with valid, usable value\n\nThe ? operator checks for state 3 (set with meaningful value).\n\nUSE IN CONDITIONS\n  if userName?                    single check\n  if firstName? and lastName?     compound check\n  isReady <- connection?          assign Boolean result\n\nCOLLECTION SEMANTICS\nCollections (List, Dict) are ALWAYS set when created, even if empty:\n  emptyList <- List() of Integer\n  emptyList?              true — empty list IS set\n\nSee Q24 for tri-state semantics. See Q981 for more ? examples.","ek9Example":"defines module qa.operators.issetnotternary\n\n  defines function\n\n    <?-\n      Shows ? operator checking if value is SET.\n      Like Rust: Option.is_some()\n      NOT like JavaScript: condition ? a : b\n    -?>\n    describeState() as pure\n      -> userName as String\n      <- rtn as String: String()\n\n      // ? checks if userName is SET (has meaningful value)\n      if userName?\n        rtn := `User: ${userName}`\n      else\n        rtn := \"No user set\"\n\n    <?-\n      Shows guard expression as alternative to ternary.\n      EK9 has no ternary — use :=? instead.\n    -?>\n    getDisplayName() as pure\n      ->\n        firstName as String\n        fallbackName as String\n      <-\n        displayName as String: String()\n\n      // :=? only assigns if displayName is currently UNSET\n      displayName :=? firstName\n      displayName :=? fallbackName\n      displayName :=? \"Anonymous\"\n\n  defines program\n\n    IsSetDemo()\n      stdout <- Stdout()\n\n      // ? operator — like Rust's is_some()\n      emptyName <- String()\n      stdout.println(describeState(emptyName))\n\n      filledName <- \"Steve\"\n      stdout.println(describeState(filledName))\n\n      // Collections are ALWAYS set when created\n      // Note: List() is always set, so no need for isSet check\n      emptyNumbers <- List() of Integer\n      emptyCount <- length emptyNumbers\n      stdout.println(`Empty list has ${emptyCount} items (but IS set)`)\n\n      // Guard expression instead of ternary\n      displayA <- getDisplayName(\"Alice\", \"Default\")\n      stdout.println(`Display: ${displayA}`)\n\n      displayB <- getDisplayName(String(), \"Fallback\")\n      stdout.println(`Display: ${displayB}`)","migrationContext":"EK9's suffix ? checks if a value is set — append it after any variable name. Returns Boolean, no parentheses needed.","keywords":["Option","Rust","check","isSet","operator","question","set","suffix","tri-state","unset"],"primaryTopics":["? operator","isSet","tri-state","suffix operator"],"typicalErrors":[{"error":"E01073","correct":"      if userName?\n        rtn := `User: ${userName}`","incorrect":"      if userName != null\n        stdout.println(userName)","explanation":"'null' does not exist in EK9. Append ? after the variable to check if it is set: 'if userName?' See ek9 -h E01073."}],"companions":[]}
{"id":899,"category":"Operators and Expressions","question":"What are the coalescing operators in EK9?","url":"https://ek9.io/qa/QA0899.html","alternatePhrasings":["How do coalescing comparators work in EK9?","What do <? and >? mean in EK9?","What is the ?? operator in EK9?","How does null coalescing work in EK9?"],"answer":"EK9 has coalescing COMPARISON operators that return a value while handling UNSET gracefully. These are unique to EK9.\n\n?? and ?: — VALUE COALESCING\nReturns the left value if SET, otherwise the right:\n  result <- maybeValue ?? defaultValue\n  result <- maybeValue ?: fallback\n\nCOALESCING COMPARATORS (unique to EK9)\nThese COMPARE two values and RETURN a result. If either is UNSET, return the SET one:\n  <? — coalescing minimum: returns the SMALLER value\n  >? — coalescing maximum: returns the LARGER value\n  <=? — coalescing less-or-equal minimum\n  >=? — coalescing greater-or-equal maximum\n\nExamples:\n  bestPrice <- price1 <? price2    returns the cheaper price\n  topScore <- score1 >? score2     returns the higher score\n\nAll coalescing operators RETURN a value — use them on the RIGHT side of <-.\n\nNOTE: :=? is NOT a coalescing operator. It is an ASSIGNMENT operator (see Q1152). :=? assigns a value only if the target is unset. Do not confuse :=? (assignment) with <? (comparison).\n\nSee Q24 for tri-state semantics. See Q898 for the ? operator. See Q1226 for <? vs :=? contrast.","ek9Example":"defines module qa.operators.coalescingdetail\n\n  defines function\n\n    <?-\n      Shows ?? value coalescing.\n      Like Kotlin's ?: or JavaScript's ??\n    -?>\n    getDisplayName() as pure\n      ->\n        userName as String\n        defaultName as String\n      <-\n        rtn as String: userName ?? defaultName\n\n    <?-\n      Shows <? coalescing minimum.\n      Unique to EK9 — returns smaller, or the SET one.\n    -?>\n    cheaperPrice() as pure\n      ->\n        priceA as Float\n        priceB as Float\n      <-\n        rtn as Float: priceA <? priceB\n\n    <?-\n      Shows >? coalescing maximum.\n      Unique to EK9 — returns larger, or the SET one.\n    -?>\n    higherScore() as pure\n      ->\n        scoreA as Integer\n        scoreB as Integer\n      <-\n        rtn as Integer: scoreA >? scoreB\n\n  defines program\n\n    CoalescingDemo()\n      stdout <- Stdout()\n\n      // ?? value coalescing — like Kotlin's ?: or JS's ??\n      userName <- String()\n      displayName <- getDisplayName(userName, \"Guest\")\n      stdout.println(`Display: ${displayName}`)\n\n      setName <- \"Alice\"\n      displayName2 <- getDisplayName(setName, \"Guest\")\n      stdout.println(`Display: ${displayName2}`)\n\n      // <? coalescing minimum — RETURNS a value\n      cheapest <- cheaperPrice(29.99, 19.99)\n      stdout.println(`Cheapest: ${cheapest}`)\n\n      // >? coalescing maximum\n      topScore <- higherScore(85, 92)\n      stdout.println(`Top: ${topScore}`)","migrationContext":"Kotlin: ?: elvis operator — similar to EK9's ??. JavaScript: ?? nullish coalescing — similar to EK9's ??. Swift: ?? nil coalescing — similar to EK9's ??. C#: ?? null coalescing — similar to EK9's ??. None of these languages have coalescing COMPARATORS (<?, >?, <=?, >=?) — these are unique to EK9.","keywords":["coalesce","coalescing","comparator","elvis","maximum","minimum","null","unset"],"primaryTopics":["coalescing operators","?? operator","<? >? operators"],"typicalErrors":[],"companions":[]}
{"id":900,"category":"Classes and OOP","question":"What is the difference between record and class field visibility in EK9?","url":"https://ek9.io/qa/QA0900.html","alternatePhrasings":["Are record fields public or private in EK9?","Why can I access record fields but not class fields?","How does visibility differ between records and classes in EK9?","What is the default visibility for EK9 records vs classes?"],"answer":"EK9 records and classes have OPPOSITE default field visibility. This catches many developers coming from Java where both are the same.\n\nRECORD FIELDS: PUBLIC by default\nLike Kotlin data classes or Rust structs, record fields are directly accessible:\n  defines record\n    Point\n      x as Float: 0.0\n      y as Float: 0.0\n\n  origin <- Point()\n  xValue <- origin.x         OK — fields are public\n\nCLASS FIELDS: PRIVATE by default\nLike Java, class fields are encapsulated — access through methods only:\n  defines class\n    Person\n      name <- String()\n\n  person <- Person()\n  person.name               ERROR — field is private\n  person.getName()           OK — access through method\n\nWHY THE DIFFERENCE\n- Records are VALUE TYPES — transparent data carriers like database rows or JSON objects\n- Classes are BEHAVIOR TYPES — encapsulated objects with invariants to protect\n- Records can't have additional methods (only operators)\n- Classes can have methods and protect internal state\n\nRECORD RULES\n- Records CAN have operators (default operator generates them)\n- Records CANNOT have additional methods\n- Records are closed-by-default like classes — mark a base record 'as open' to allow extension (see Q1297 for the full record-inheritance pattern)\n- Record fields are PUBLIC — there's nothing to protect since records are transparent\n- Records are mutable; mutability is gated by 'as pure' on functions and methods, not on the type (see Q97)\n\nCLASS FEATURES\n- Classes CAN have methods and operators\n- Class fields are PRIVATE — encapsulation protects invariants\n- Classes can use 'as open' to allow extension (same rule as records)\n- Classes can implement traits\n\nSee Q93 for class basics. See Q97 for records vs classes overview. See Q1297 for record-to-record inheritance.","ek9Example":"defines module qa.classes.recordvsclass\n\n  defines record\n\n    <?-\n      Record fields are PUBLIC — transparent data carrier.\n      Like Kotlin data class or Rust struct with pub fields.\n    -?>\n    Measurement\n      sensorId as String: String()\n      reading as Float: 0.0\n      timestamp as Integer: 0\n\n      Measurement()\n        ->\n          sensorId as String\n          reading as Float\n          timestamp as Integer\n        this.sensorId :=: sensorId\n        this.reading :=: reading\n        this.timestamp :=: timestamp\n\n      default operator\n\n  defines class\n\n    <?-\n      Class fields are PRIVATE — encapsulated behavior.\n      Access through methods only, like Java.\n    -?>\n    SensorMonitor\n      sensorName as String: String()\n      latestReading as Float: 0.0\n      readingCount as Integer: 0\n\n      SensorMonitor()\n        -> sensorName as String\n        this.sensorName :=: sensorName\n\n      recordReading()\n        -> newReading as Float\n        latestReading :=: newReading\n        readingCount++\n\n      getLatestReading() as pure\n        <- rtn as Float: latestReading\n\n      getReadingCount() as pure\n        <- rtn as Integer: readingCount\n\n      getSensorName() as pure\n        <- rtn as String: sensorName\n\n      default operator\n\n  defines program\n\n    RecordVsClassDemo()\n      stdout <- Stdout()\n\n      // RECORD: fields are PUBLIC — direct access\n      measurement <- Measurement(\"sensor-1\", 23.5, 1000)\n      stdout.println(`Sensor: ${measurement.sensorId}`)\n      readingVal <- measurement.reading\n      timeVal <- measurement.timestamp\n      stdout.println(`Reading: ${readingVal}`)\n      stdout.println(`Time: ${timeVal}`)\n\n      // CLASS: fields are PRIVATE — access through methods\n      monitor <- SensorMonitor(\"temperature\")\n      monitor.recordReading(23.5)\n      monitor.recordReading(24.1)\n      stdout.println(`Monitor: ${monitor.getSensorName()}`)\n      stdout.println(`Latest: ${monitor.getLatestReading()}`)\n      stdout.println(`Count: ${monitor.getReadingCount()}`)","migrationContext":"Java: both class and record fields are private by default. Kotlin: data class properties are public, class properties are public unless private. Rust: struct fields are private by default, pub required. Go: exported if capitalized (uppercase), unexported if lowercase — applies to both struct and interface fields. Python: all attributes are public, _prefix convention for private. EK9: records are PUBLIC (like Kotlin data class), classes are PRIVATE (like Java) — opposite defaults based on type purpose.","keywords":["access","class","encapsulation","field","private","public","record","visibility"],"primaryTopics":["record visibility","class visibility","public vs private"],"typicalErrors":[{"error":"E06180","correct":"monitor.getSensorName()","incorrect":"monitor.sensorName","explanation":"Class fields are PRIVATE by default in EK9 — access them through methods (getSensorName()), not by direct field reference. See ek9 -h E06180 for details."}],"companions":[]}
{"id":901,"category":"Control Flow","question":"Can switch be used as an expression in EK9?","url":"https://ek9.io/qa/QA0901.html","alternatePhrasings":["Is switch an expression or statement in EK9?","How do I assign from a switch in EK9?","Does EK9 switch return a value?","How does switch work as an expression in EK9?"],"answer":"YES — switch CAN be an expression in EK9. Like Kotlin's 'when' expression, EK9's switch can produce a value that gets assigned.\n\nSWITCH AS EXPRESSION\nUse <- to capture the switch result. The switch needs a return declaration:\n  label <- switch category\n    <- rtn as String: String()\n    case \"A\"\n      rtn: \"Alpha\"\n    default\n      rtn: \"Unknown\"\n\nThe '<- rtn as String' declares the return variable. Each case assigns to rtn.\n\nSWITCH AS STATEMENT\nWithout <-, switch is a statement:\n  switch direction\n    case \"north\"\n      moveNorth()\n    default\n      stay()\n\nNO FALLTHROUGH\nEK9 has NO switch fallthrough — each case is independent. No break needed because break does not exist. Multiple values on one case:\n  switch dayOfWeek\n    case \"MONDAY\", \"TUESDAY\", \"WEDNESDAY\"\n      rtn: \"weekday\"\n    case \"SATURDAY\", \"SUNDAY\"\n      rtn: \"weekend\"\n\nSee Q63 for switch basics. See Q68 for switch as expression. See Q782 for switch return rules.","ek9Example":"defines module qa.controlflow.switchexpr\n\n  defines function\n\n    <?-\n      Switch as expression — like Kotlin's when.\n      Needs <- rtn declaration inside switch.\n    -?>\n    classifyTemperature() as pure\n      -> degrees as Float\n      <-\n        rtn as String: String()\n\n      rtn := switch degrees\n        <- category as String: String()\n        case < 0.0\n          category: \"freezing\"\n        case < 10.0\n          category: \"cold\"\n        case < 20.0\n          category: \"cool\"\n        case < 30.0\n          category: \"warm\"\n        default\n          category: \"hot\"\n\n    <?-\n      Multiple case values — no fallthrough needed.\n    -?>\n    isWeekend() as pure\n      -> dayName as String\n      <-\n        rtn as Boolean: false\n\n      rtn := switch dayName\n        <- weekend as Boolean: false\n        case \"SATURDAY\", \"SUNDAY\"\n          weekend: true\n        default\n          weekend: false\n\n    <?-\n      Switch as expression returning String.\n    -?>\n    describeSeason() as pure\n      -> month as Integer\n      <-\n        rtn as String: String()\n\n      rtn := switch month\n        <- seasonName as String: String()\n        case 3, 4, 5\n          seasonName: \"Spring\"\n        case 6, 7, 8\n          seasonName: \"Summer\"\n        case 9, 10, 11\n          seasonName: \"Autumn\"\n        default\n          seasonName: \"Winter\"\n\n  defines program\n\n    SwitchExpressionDemo()\n      stdout <- Stdout()\n\n      forecast <- classifyTemperature(15.5)\n      stdout.println(`15.5 degrees is: ${forecast}`)\n\n      hot <- classifyTemperature(35.0)\n      stdout.println(`35.0 degrees is: ${hot}`)\n\n      saturdayWeekend <- isWeekend(\"SATURDAY\")\n      stdout.println(`Saturday weekend: ${saturdayWeekend}`)\n\n      mondayWeekend <- isWeekend(\"MONDAY\")\n      stdout.println(`Monday weekend: ${mondayWeekend}`)\n\n      season <- describeSeason(7)\n      stdout.println(season)","migrationContext":"Kotlin: when expression — EK9 switch as expression works similarly but needs explicit return declaration. Java: switch expressions (Java 14+) with -> — similar concept. Rust: match expression — similar, always returns a value. Go: switch is a statement, not an expression. Python: match (3.10+) is a statement. C: switch with mandatory break, fallthrough by default — EK9 has NO fallthrough and NO break. EK9: switch is BOTH expression and statement, like Kotlin's when. No fallthrough, no break needed.","keywords":["Kotlin","case","default","expression","fallthrough","return","statement","switch","when"],"primaryTopics":["switch expression","switch statement","no fallthrough"],"typicalErrors":[],"companions":[]}
{"id":902,"category":"Control Flow","question":"How do try/catch/finally work in EK9?","url":"https://ek9.io/qa/QA0902.html","alternatePhrasings":["How do I catch exceptions in EK9?","What is the catch syntax in EK9?","Does EK9 have try/catch?","How do I handle exceptions in EK9?"],"answer":"EK9 has try/catch/finally similar to Java, but the catch clause uses '->' for the exception parameter — like Go's 'in' parameter or Ada's 'in' port.\n\nBASIC TRY/CATCH\n  try\n    riskyOperation()\n  catch\n    -> ex as Exception\n    stdout.println(`Error: ${ex}`)      the -> means 'data coming in'\n\nIMPORTANT: The catch uses '->' on its own line, like function parameters. The exception is data flowing IN to the catch handler.\n\nTRY/CATCH/FINALLY\n  try\n    openConnection()\n    sendRequest()\n  catch\n    -> ex as Exception\n    logError(ex)\n  finally\n    closeConnection()    always runs, like Java's finally\n\nTRY AS EXPRESSION\nLike switch, try can be an expression:\n  result <- try\n    parseInput(rawText)\n  catch\n    -> ex as Exception\n    \"default value\"\n\nGUARD TRY\nTry can use a guard variable:\n  try result <- riskyComputation()\n    stdout.println(result)\n  catch\n    -> ex as Exception\n    stdout.println(`Failed: ${ex}`)\n\nEXCEPTION TYPES\nEK9 has a fixed exception hierarchy:\n- Exception (base type)\nUse '$' on an exception to get its message as String.\n\nNO CHECKED EXCEPTIONS\nEK9 does not have checked exceptions. All exceptions are runtime.\n\nSee Q130 for control flow. See Q893 for section headers.","ek9Example":"defines module qa.controlflow.trycatch\n\n  defines function\n\n    <?-\n      Basic function that may throw.\n    -?>\n    safeDivide() as pure\n      ->\n        numerator as Integer\n        denominator as Integer\n      <-\n        rtn as Float: #^ numerator / #^ denominator\n\n  defines program\n\n    TryCatchDemo()\n      stdout <- Stdout()\n\n      // Basic try/catch — note -> on catch for exception parameter\n      // Like Go/Ada: -> means 'data flowing in' to the handler\n      try\n        quotient <- safeDivide(10, 2)\n        stdout.println(`10 / 2 = ${quotient}`)\n      catch\n        -> ex as Exception\n        stdout.println(`Error: ${ex}`)\n\n      // Try/catch/finally — finally always runs\n      try\n        result <- safeDivide(100, 4)\n        stdout.println(`100 / 4 = ${result}`)\n      catch\n        -> ex as Exception\n        stdout.println(`Failed: ${ex}`)\n      finally\n        stdout.println(\"Division complete\")\n\n      // Nested try/catch — works like Java\n      try\n        firstResult <- safeDivide(20, 5)\n        stdout.println(`20 / 5 = ${firstResult}`)\n      catch\n        -> ex as Exception\n        stdout.println(`Nested failed: ${ex}`)","migrationContext":"Java: catch (Exception e) { ... } — EK9 uses -> ex as Exception on separate line. Go: if err != nil — EK9 has try/catch, not error values. Python: except Exception as e: — similar but EK9 uses -> prefix. Kotlin: catch (e: Exception) — EK9 uses -> for incoming data. Rust: Result and ? operator — EK9 has both try/catch and Result type. EK9: catch with -> ex as Exception — the -> means 'data flowing in' like Go/Ada parameter ports.","keywords":["arrow","catch","data-in","error","exception","finally","handle","throw","try"],"primaryTopics":["try/catch","exception handling","catch arrow syntax"],"typicalErrors":[{"error":"E04030","correct":"-> ex as Exception","incorrect":"-> ex as String","explanation":"The catch clause requires an Exception type, not String or other types. The variable after -> must be declared 'as Exception'. See ek9 -h E04030 for details."}],"companions":[]}
{"id":903,"category":"What AI Gets Wrong About EK9","question":"What operators do NOT exist in EK9?","url":"https://ek9.io/qa/QA0903.html","alternatePhrasings":["What operators are invalid in EK9?","What is the complete list of EK9 operators?","Does EK9 have array index operators?","What common operators from other languages are absent in EK9?"],"answer":"EK9 has a FIXED set of approximately 50 operators. Only these exist — no others can be created or used.\n\nTHE COMPLETE EK9 OPERATOR SET\nAssignment: <- (declaration), := : = (assignment), :=? (guarded assignment)\nCopy/Replace/Merge: :=: (copy), :^: (replace), :~: (merge)\nComparison: == (eq), <> (neq), <=> (compare), < > <= >=\nCoalescing: ?? (value), <? >? <=? >=? (coalescing comparators)\nArithmetic: + - * / mod\nMutation: ++ -- += -= (postfix statement-only)\nConversion: $ (string via _string), #? (hashcode via _hashcode), #^ (promote via _promote)\nChecking: ? (isSet via _isSet), ~ (negate/bitwise not)\nLogical: not and or xor\nCollection/String: contains matches\nUnary: length abs sqrt close empty\n\nCORRECT ALTERNATIVES FOR PATTERNS FROM OTHER LANGUAGES\nConditional value: use switch expression or if/else assignment\nCollection access: use get() method on List/Dict\nSafe value access: use guard expression (if v <- expr())\nBit shifting: use Bits type\nPrefix increment: use postfix (variable++ not ++variable)\n\nRULE: If an operator is not in this list, it does not exist in EK9.\n\nSee Q96 for operator definitions. See Q238 for operator enforcement rules.","ek9Example":"defines module qa.aimistakes.invalidoperators\n\n  defines function\n\n    <?-\n      Shows correct alternatives to operators that don't exist.\n    -?>\n    classify() as pure\n      -> score as Integer\n      <-\n        rtn as String: String()\n\n      rtn := switch score\n        <- grade as String: String()\n        case >= 90\n          grade: \"excellent\"\n        case >= 70\n          grade: \"good\"\n        case >= 50\n          grade: \"pass\"\n        default\n          grade: \"fail\"\n\n    <?-\n      Shows correct list access — no [] operator in EK9.\n      Use get() method, not [] indexing.\n    -?>\n    getItemCount() as pure\n      -> items as List of String\n      <- rtn as Integer: length items\n\n    <?-\n      Shows correct approach instead of safe navigation (?.)\n      Use guard expression instead.\n    -?>\n    safeLength() as pure\n      -> text as String\n      <- rtn as Integer: 0\n\n      if text?\n        rtn := length text\n\n  defines program\n\n    CorrectOperatorsDemo()\n      stdout <- Stdout()\n\n      // Switch expression — NOT ternary\n      grade <- classify(85)\n      stdout.println(`Grade: ${grade}`)\n\n      // Method call — NOT [] indexing\n      names <- [\"Alice\", \"Bob\", \"Charlie\"]\n      nameCount <- getItemCount(names)\n      stdout.println(`Names: ${nameCount}`)\n\n      // Guard expression — NOT ?. safe navigation\n      textLength <- safeLength(\"hello\")\n      stdout.println(`Length: ${textLength}`)\n\n      emptyLength <- safeLength(String())\n      stdout.println(`Empty length: ${emptyLength}`)\n\n      // Postfix ++ only — NOT prefix ++\n      counter <- 0\n      counter++\n      counter++\n      stdout.println(`Counter: ${counter}`)","migrationContext":"EK9 has a fixed operator set. Collection access uses get() methods. Conditional values use switch expressions. Safe access uses guard expressions.","keywords":["AI","complete list","fixed set","indexing","mistake","operator","valid operators"],"primaryTopics":["invalid operators","operator hallucination","correct alternatives"],"typicalErrors":[],"companions":[]}
{"id":904,"category":"Classes and OOP","question":"Why can't I extend a class in EK9?","url":"https://ek9.io/qa/QA0904.html","alternatePhrasings":["What does 'closed by default' mean in EK9?","How do I make a class extensible in EK9?","What is the 'as open' modifier in EK9?","Why does E05030 say 'not open to be extended'?"],"answer":"EK9 types are CLOSED by default — like Kotlin's classes which are final unless marked 'open'. You must explicitly opt into extensibility with 'as open'.\n\nCLOSED BY DEFAULT\nAll classes, records, and built-in types cannot be extended unless marked:\n  defines class\n    Animal          closed — cannot extend\n      name <- String()\n\n    Dog extends Animal   ERROR E05030 — Animal is not open\n\nOPT INTO EXTENSION\nUse 'as open' to allow extension:\n  defines class\n    Animal as open       now extensible\n      name <- String()\n\n    Dog extends Animal   OK — Animal is open\n\nBUILT-IN TYPES CANNOT BE EXTENDED\nList, Dict, Optional, Result, and all built-in concrete types are CLOSED:\n  MyList extends List of String    ERROR — List is closed\n\nUse composition/delegation instead:\n  defines class\n    ValidatedList\n      items as List of String: List() of String\n      addValidated()\n        -> item as String\n        if length item > 0\n          items += item\n\nWHY CLOSED BY DEFAULT\n- Like Kotlin's final by default — prevents fragile base class problem\n- Forces explicit design for inheritance\n- Encourages composition over inheritance (more flexible)\n- Simplifies generated code (no virtual dispatch needed)\n- Modern consensus: Java's open-by-default was a design mistake\n\nRECORDS ARE ALWAYS CLOSED\nRecords cannot be marked 'as open' — they are pure value types:\n  defines record\n    Point\n      x as Float: 0.0\n      y as Float: 0.0\n\nSee Q93 for class basics. See Q97 for records vs classes.","ek9Example":"defines module qa.classes.closedtypes\n\n  defines class\n\n    <?-\n      CLOSED class — cannot be extended.\n      Like Kotlin's final (default behavior).\n    -?>\n    DatabaseConnection\n      connectionUrl as String: String()\n\n      DatabaseConnection()\n        -> connectionUrl as String\n        this.connectionUrl :=: connectionUrl\n\n      getUrl() as pure\n        <- rtn as String: connectionUrl\n\n      default operator\n\n    <?-\n      OPEN class — explicitly allows extension.\n      Like Kotlin's 'open class'.\n    -?>\n    Shape as abstract\n      shapeName as String: String()\n\n      Shape()\n        -> shapeName as String\n        this.shapeName :=: shapeName\n\n      area() as pure abstract\n        <- rtn as Float?\n\n      getName() as pure\n        <- rtn as String: shapeName\n\n      default operator\n\n    <?-\n      Extends an open class — this is allowed.\n    -?>\n    Rectangle extends Shape\n      rectWidth as Float: 0.0\n      rectHeight as Float: 0.0\n\n      Rectangle()\n        ->\n          rectWidth as Float\n          rectHeight as Float\n        super(\"Rectangle\")\n        this.rectWidth :=: rectWidth\n        this.rectHeight :=: rectHeight\n\n      override area() as pure\n        <- rtn as Float: rectWidth * rectHeight\n\n      default operator\n\n    <?-\n      Composition instead of inheritance.\n      Used when the base type is closed.\n    -?>\n    ValidatedItems\n      items as List of String: List() of String\n\n      addItem()\n        -> item as String\n        if length item > 0\n          items += item\n\n      getCount() as pure\n        <- rtn as Integer: length items\n\n      default operator\n\n  defines program\n\n    ClosedTypesDemo()\n      stdout <- Stdout()\n\n      // Closed class — used directly, not extended\n      conn <- DatabaseConnection(\"jdbc:postgresql://localhost:5432/mydb\")\n      stdout.println(`Connection: ${conn.getUrl()}`)\n\n      // Open class — can be extended\n      rect <- Rectangle(5.0, 3.0)\n      stdout.println(`Shape: ${rect.getName()}, Area: ${rect.area()}`)\n\n      // Composition — when you can't extend\n      validItems <- ValidatedItems()\n      validItems.addItem(\"hello\")\n      validItems.addItem(\"\")\n      validItems.addItem(\"world\")\n      stdout.println(`Valid items: ${validItems.getCount()}`)","migrationContext":"Kotlin: classes are final by default, use 'open' keyword — identical to EK9's approach. Java: classes are open by default — opposite of EK9, considered a historical design mistake. Rust: no inheritance at all — composition via traits only. Swift: classes are not final by default but 'final' keyword available. Go: no inheritance — composition only. C++: classes are extensible by default. EK9: closed by default like Kotlin — use 'as open' to allow extension.","keywords":["E05030","Kotlin","closed","composition","extend","final","inherit","open"],"primaryTopics":["closed by default","as open","composition over inheritance"],"typicalErrors":[],"companions":[]}
{"id":905,"category":"Operators and Expressions","question":"What happens when I use mutating operators in EK9?","url":"https://ek9.io/qa/QA0905.html","alternatePhrasings":["Do mutating operators create aliases in EK9?","What does += return in EK9?","Why does y <- x++ make x and y the same object?","How do mutating operators work in EK9?"],"answer":"Mutating operators in EK9 return 'this' — the SAME object, not a copy. This creates aliasing, similar to C++ references or Java mutable objects.\n\nMUTATING OPERATORS\nThese operators modify the object IN PLACE and return the same object:\n  += -= *= /= — compound arithmetic\n  ++ -- — increment/decrement\n  :=: — copy (overwrites all fields)\n  :^: — replace\n  :~: — merge\n\nALIASING DANGER\n  x <- 10\n  y <- x++    ERROR — ++ is statement-only in EK9\n\nEK9 prevents this by making ++ and -- STATEMENT-ONLY operators. You CANNOT use them in expressions.\n\nCORRECT USAGE\n  counter <- 0\n  counter++         OK — statement\n  counter += 5      OK — statement\n\n  // To get a copy, use a separate declaration\n  original <- 100\n  copied <- original    creates independent copy\n  copied++              only copied changes\n\nNON-MUTATING OPERATORS\nThese create NEW objects and do NOT alias:\n  + - * / — arithmetic (return new value)\n  == <> < > <= >= — comparison (return Boolean)\n  <=> — three-way compare (return Integer)\n  $ — string conversion (return String)\n  #? — hashcode (return Integer)\n  #^ — promote (return wider type)\n  ? — isSet (return Boolean)\n\nRULE: If the operator changes the object, it is mutating and statement-only. If it produces a new value, it is non-mutating and can be used in expressions.\n\nSee Q96 for all operators. See Q238 for operator enforcement.","ek9Example":"defines module qa.operators.mutating\n\n  defines function\n\n    <?-\n      Shows correct use of mutating operators as statements.\n      ++ -- += -= are STATEMENT-ONLY, not expressions.\n    -?>\n    accumulateTotal()\n      -> numbers as List of Integer\n      <- runningTotal as Integer: 0\n\n      for n in numbers\n        runningTotal += n\n\n    <?-\n      Shows non-mutating operators that create new values.\n      + - * / return NEW objects, safe in expressions.\n    -?>\n    calculateAverage() as pure\n      ->\n        total as Integer\n        divisor as Integer\n      <-\n        rtn as Float: 0.0\n\n      if divisor > 0\n        rtn := #^ total / #^ divisor\n\n  defines program\n\n    MutatingOperatorsDemo()\n      stdout <- Stdout()\n\n      // Mutating operators are STATEMENT-ONLY\n      counter <- 0\n      counter++\n      counter++\n      counter++\n      stdout.println(`Counter after 3 increments: ${counter}`)\n\n      counter += 10\n      stdout.println(`Counter after += 10: ${counter}`)\n\n      counter--\n      stdout.println(`Counter after --: ${counter}`)\n\n      // Non-mutating operators create NEW values (safe in expressions)\n      productA <- 5 * 3\n      productB <- 7 + 2\n      stdout.println(`5 * 3 = ${productA}, 7 + 2 = ${productB}`)\n\n      // Copying creates independent values\n      original <- 100\n      copied <- original\n      copied += 50\n      stdout.println(`Original: ${original}, Copied: ${copied}`)\n\n      // Accumulation with +=\n      numbers <- [10, 20, 30, 40]\n      total <- accumulateTotal(numbers)\n      avg <- calculateAverage(total, length numbers)\n      stdout.println(`Total: ${total}, Average: ${avg}`)","migrationContext":"C++: mutating operators return references (aliasing risk). Java: += modifies in place, primitives are copied but objects are aliased. Rust: no implicit aliasing — ownership system prevents it. Go: no operator overloading. Python: += modifies in place for mutable types (lists), creates new for immutable (int). EK9: mutating operators are STATEMENT-ONLY — prevents aliasing bugs at compile time.","keywords":["aliasing","compound","copy","decrement","expression","increment","mutating","operator","statement"],"primaryTopics":["mutating operators","aliasing","statement-only operators"],"typicalErrors":[{"error":"E07950","correct":"copied <- original","incorrect":"copied <- original++","explanation":"The ++ operator is statement-only and cannot appear inside an expression; increment first, then use the value. See ek9 -h E07950 for details."}],"companions":[]}
{"id":906,"category":"Variable Naming Rules and Conventions","question":"What should I use instead of banned variable names in EK9?","url":"https://ek9.io/qa/QA0906.html","alternatePhrasings":["What are good alternatives to temp, value, data in EK9?","How do I rename banned variables in EK9?","What descriptive names replace banned identifiers?","Give me a renaming table for EK9 banned names"],"answer":"EK9 bans generic variable names at compile time (E11031). Here is a complete replacement guide.\n\nREPLACEMENT TABLE\nInstead of 'temp' use: swapHolder, intermediateResult, transformedInput\nInstead of 'value' use: price, score, threshold, measurement, reading\nInstead of 'data' use: payload, sensorReading, customerRecord, configEntry\nInstead of 'flag' use: isValid, hasPermission, isActive, wasProcessed\nInstead of 'buffer' use: inputBuffer, readChunk, outputAccumulator\nInstead of 'result' use: computedScore, lookupOutcome, validationStatus\nInstead of 'count' use: itemCount, retryAttempt, errorTally\nInstead of 'index' use: currentPosition, insertionPoint, searchOffset\nInstead of 'item' use: currentOrder, selectedProduct, queueEntry\nInstead of 'obj' use: the specific type name — person, connection, response\nInstead of 'str' use: formattedName, rawInput, encodedPayload\nInstead of 'num' use: quantity, threshold, portNumber\n\nTHE PRINCIPLE\nThe name should answer: WHAT does this represent in the problem domain? Not WHAT type is it.\n\nBAD: temp <- customer.getAddress()    what IS temp?\nGOOD: billingAddress <- customer.getAddress()    clear purpose\n\nBAD: data <- sensor.read()    could be anything\nGOOD: temperatureReading <- sensor.read()    obvious meaning\n\nALWAYS ALLOWED\n- Single characters: x, y, z, i, j, k (math and loop convention)\n- Compound words containing banned words: connectionState, errorHandler, dataProcessor\n- Type-qualified names: the type carries semantics (amount as Money, thrust as Dimension)\n\nSee Q290 for the complete banned list. See Q296 for research evidence.","ek9Example":"defines module qa.naming.alternatives\n\n  defines function\n\n    <?-\n      Shows descriptive alternatives to banned names.\n      Every variable name tells you what it represents.\n    -?>\n    processOrder() as pure\n      ->\n        unitPrice as Float\n        orderQuantity as Integer\n        discountPercent as Float\n      <-\n        finalPrice as Float: 0.0\n\n      // NOT: temp <- unitPrice * orderQuantity\n      subtotal <- unitPrice * #^ orderQuantity\n\n      // NOT: value <- subtotal * (1.0 - discountPercent)\n      discountedAmount <- subtotal * (1.0 - discountPercent)\n\n      finalPrice := discountedAmount\n\n    <?-\n      Shows compound words with banned roots — these ARE allowed.\n    -?>\n    describeConnection() as pure\n      ->\n        connectionState as String\n        errorHandler as String\n        dataProcessor as String\n      <-\n        rtn as String: `${connectionState}: ${errorHandler} via ${dataProcessor}`\n\n  defines program\n\n    NamingAlternativesDemo()\n      stdout <- Stdout()\n\n      // GOOD: descriptive names\n      totalPrice <- processOrder(29.99, 3, 0.1)\n      stdout.println(`Order total: ${totalPrice}`)\n\n      // GOOD: compound words with banned roots are allowed\n      summary <- describeConnection(\"active\", \"retryHandler\", \"jsonProcessor\")\n      stdout.println(summary)\n\n      // GOOD: single-character math variables\n      x <- 3.0\n      y <- 4.0\n      hypotenuse <- sqrt(x * x + y * y)\n      stdout.println(`Hypotenuse: ${hypotenuse}`)\n\n      // GOOD: type carries semantics\n      orderCount <- 5\n      isActive <- true\n      customerName <- \"Alice\"\n      stdout.println(`${customerName}: ${orderCount} orders, active=${isActive}`)","migrationContext":"Java: no naming enforcement, relies on optional SonarQube rules. Python: PEP 8 naming is voluntary. Rust: clippy provides optional naming suggestions. Go: golint suggests naming conventions. EK9: compile-time naming enforcement — generic names are compiler errors, not warnings.","keywords":["E11031","alternative","banned","descriptive","naming","rename","replace","variable"],"primaryTopics":["banned name alternatives","descriptive naming","renaming guide"],"typicalErrors":[{"error":"E11031","correct":"      hypotenuse <- sqrt(x * x + y * y)\n      stdout.println(`Hypotenuse: ${hypotenuse}`)","incorrect":"      data <- sqrt(x * x + y * y)\n      stdout.println(`Hypotenuse: ${data}`)","explanation":"'data' is a banned non-descriptive variable name in EK9 — use a name that says what it represents (e.g. hypotenuse). See ek9 -h E11031 for details."}],"companions":[]}
{"id":907,"category":"Syntax and Structure Rules","question":"How is EK9 structured compared to Pascal?","url":"https://ek9.io/qa/QA0907.html","alternatePhrasings":["What is the Pascal-like structure of EK9?","How does EK9 code organization compare to other languages?","What is the module structure of EK9?","How do I organize an EK9 source file?"],"answer":"EK9 source files have a Pascal-like structure with mandatory section headers. Like Pascal's type/var/procedure/function sections, EK9 uses 'defines class/record/function/program' sections.\n\nFILE STRUCTURE (top to bottom)\n1. #!ek9 — shebang line (required)\n2. defines module — module declaration (like Pascal's unit/program name)\n3. defines constant — constants (like Pascal's const section)\n4. defines type — enumerations and constrained types (like Pascal's type section)\n5. defines record — value types (like Pascal's record types)\n6. defines function — standalone functions (like Pascal's function/procedure section)\n7. defines trait — interfaces (no Pascal equivalent — like Java interfaces)\n8. defines class — classes (like Pascal's object types)\n9. defines component — DI-injectable components\n10. defines program — entry points (like Pascal's program body)\n11. //EOF — end marker (like Pascal's end.)\n\nPASCAL COMPARISON\nPascal:                       EK9:\n  unit MyUnit;                  defines module my.package\n  type                          defines record\n    TPoint = record               Point\n      x: Real;                      x as Float: 0.0\n      y: Real;                      y as Float: 0.0\n    end;\n  var                           defines constant\n    MaxSize: Integer = 100;       MAX_SIZE <- 100\n  procedure DoWork;             defines function\n                                  doWork()\n  begin ... end.                defines program\n                                  Main()\n\nKEY DIFFERENCES FROM PASCAL\n- No semicolons or 'begin/end' — indentation defines scope\n- No 'var' section — variables declared inline with <-\n- Multiple sections of same type allowed (for grouping)\n- Sections can appear in any order (except 'defines module' must be first)\n\nSee Q893 for section header details. See Q93 for class definitions.","ek9Example":"defines module qa.syntax.pascalstructure\n\n  defines constant\n\n    <?-\n      Constants section — like Pascal's 'const' section.\n    -?>\n    MAX_RETRIES <- 3\n    GREETING_PREFIX <- \"Hello\"\n\n  defines record\n\n    <?-\n      Record section — like Pascal's 'type' with record types.\n    -?>\n    Dimensions\n      widthMm as Float: 0.0\n      heightMm as Float: 0.0\n\n      Dimensions()\n        ->\n          widthMm as Float\n          heightMm as Float\n        this.widthMm :=: widthMm\n        this.heightMm :=: heightMm\n\n      default operator\n\n  defines function\n\n    <?-\n      Function section — like Pascal's 'function'/'procedure'.\n    -?>\n    calculateArea() as pure\n      -> dims as Dimensions\n      <- rtn as Float: dims.widthMm * dims.heightMm\n\n    formatGreeting() as pure\n      -> recipientName as String\n      <- rtn as String: `${GREETING_PREFIX}, ${recipientName}`\n\n  defines class\n\n    <?-\n      Class section — like Pascal's 'object' type section.\n    -?>\n    RetryCounter\n      attemptNumber as Integer: 0\n\n      increment()\n        if attemptNumber < MAX_RETRIES\n          attemptNumber++\n\n      hasRetriesLeft() as pure\n        <- rtn as Boolean: attemptNumber < MAX_RETRIES\n\n      getCurrentAttempt() as pure\n        <- rtn as Integer: attemptNumber\n\n      default operator\n\n  defines program\n\n    <?-\n      Program section — like Pascal's main program body.\n    -?>\n    PascalStructureDemo()\n      stdout <- Stdout()\n\n      // Using constants\n      stdout.println(formatGreeting(\"Steve\"))\n\n      // Using records\n      boxSize <- Dimensions(200.0, 150.0)\n      areaValue <- calculateArea(boxSize)\n      stdout.println(`Box area: ${areaValue} sq mm`)\n\n      // Using classes\n      retries <- RetryCounter()\n      retries.increment()\n      retries.increment()\n      stdout.println(`Attempt: ${retries.getCurrentAttempt()}, retries left: ${retries.hasRetriesLeft()}`)","migrationContext":"Pascal: unit name; type TMyType = ...; var x: Integer; procedure DoWork; begin end. EK9 follows the same pattern with 'defines' sections. Java: no sections, classes/interfaces at top level. Python: no sections, mix everything. Go: package name; type/func/var at top level. EK9: Pascal-like 'defines class/function/record/program' mandatory sections.","keywords":["Pascal","defines","file","layout","module","organization","section","structure","unit"],"primaryTopics":["file structure","Pascal comparison","code organization"],"typicalErrors":[],"companions":[]}
{"id":908,"category":"Operators and Expressions","question":"What does the $ operator actually do in EK9?","url":"https://ek9.io/qa/QA0908.html","alternatePhrasings":["Is $ string interpolation in EK9?","Does $ mean safe access in EK9?","How do I convert a value to String in EK9?"],"answer":"The $ operator in EK9 is the STRING CONVERSION operator. It calls the _string() method on any value and returns a String representation. That is ALL it does.\n\n$ IS NOT STRING INTERPOLATION\nIn JavaScript and shell scripting, $variable inserts a value into a string. In EK9, $ only converts to String. String interpolation uses backtick strings with ${...} syntax.\n\n$ IS NOT SAFE ACCESS\nIn Kotlin, $variable inside strings does interpolation. In TypeScript, $ has no special meaning. In EK9, $ is purely _string() conversion.\n\nBASIC USAGE\n  age <- 25\n  ageText <- $age\nThis calls age._string() and stores the result \"25\" in ageText.\n\nWITH DIFFERENT TYPES\n  price <- 19.99\n  priceText <- $price       converts Float to String\n  flag <- true\n  flagText <- $flag          converts Boolean to String\n  name <- \"Steve\"\n  nameText <- $name          identity on String (already String)\n\nIN BACKTICK STRINGS\nWhen you write `Age: ${$age}`, the outer ${...} is the interpolation syntax and the inner $ converts age to String. Both are needed when interpolating non-String values.\n\nIMPLICIT CONVERSION\nInside backtick interpolation, the $ conversion is called implicitly if needed. But as a standalone operator, you must write $value explicitly.\n\nCUSTOM TYPES\nAny class can define operator $ to control its String representation. The operator must be pure and return String.\n\nSee Q897 for $ and #? together. See Q242 for all conversion operators. See Q909 for $ inside backtick strings.","ek9Example":"defines module qa.operators.dollarstringconv\n\n  defines function\n\n    describeAge() as pure\n      -> age as Integer\n      <- rtn as String: String()\n\n      // $ converts Integer to String via _string()\n      ageText <- $age\n      rtn: `Age is ${ageText}`\n\n    describePrice() as pure\n      -> price as Float\n      <- rtn as String: String()\n\n      // $ converts Float to String\n      priceText <- $price\n      rtn: `Price: ${priceText}`\n\n  defines program\n\n    DollarConversionDemo()\n      stdout <- Stdout()\n\n      // === $ converts Integer to String ===\n\n      age <- 25\n      ageText <- $age\n      stdout.println(ageText)\n\n      // === $ converts Float to String ===\n\n      price <- 19.99\n      priceText <- $price\n      stdout.println(priceText)\n\n      // === $ converts Boolean to String ===\n\n      isActive <- true\n      activeText <- $isActive\n      stdout.println(activeText)\n\n      // === Using functions that demonstrate $ ===\n\n      stdout.println(describeAge(30))\n      stdout.println(describePrice(9.99))\n\n      // === $ inside backtick: ${variable} ===\n\n      count <- 42\n      stdout.println(`Count: ${count}`)","migrationContext":"Java: toString() method call. Python: str() built-in function. JavaScript: String() or template literals with ${...}. Ruby: to_s method. Go: fmt.Sprintf or String() method. EK9: $value is a prefix operator that calls _string(), completely separate from backtick interpolation.","keywords":["_string","backtick","conversion","dollar","interpolation","operator","prefix","string"],"primaryTopics":["$ operator","string conversion","_string method"],"typicalErrors":[{"error":"E08090","correct":"ageText <- $age","incorrect":"ageText <- \"${age}\"","explanation":"Double-quoted strings do NOT support interpolation — '${age}' is a literal string, so the 'age' variable becomes unreferenced. Use backtick strings for interpolation (`${...}`) or the $ prefix operator ($age). See ek9 -h E08090 for details."}],"companions":[]}
{"id":909,"category":"Operators and Expressions","question":"How does $ work inside backtick strings in EK9?","url":"https://ek9.io/qa/QA0909.html","alternatePhrasings":["What does ${$variable} mean in EK9?","How do I interpolate non-String values in EK9 backtick strings?","Why do I need $ inside ${} in EK9?"],"answer":"In EK9 backtick strings, there are TWO separate mechanisms at work. Understanding the difference is critical.\n\nBACKTICK INTERPOLATION: ${...}\nThe ${...} syntax inside backtick strings is the INTERPOLATION mechanism. It evaluates the expression inside and inserts the result into the string. This ONLY works in backtick strings, never in double-quoted strings.\n\nDOLLAR CONVERSION: $\nThe $ operator converts any value to String by calling _string(). It is a PREFIX operator used outside or inside interpolation.\n\nCOMBINED: ${$variable}\nWhen you write `Age: ${$age}`, two things happen:\n1. The inner $age calls age._string() to convert Integer to String\n2. The outer ${...} interpolates that String result into the backtick string\n\nWHEN $ IS OPTIONAL INSIDE ${}\nFor String variables, you can write ${name} without the inner $ because the value is already a String. For non-String types (Integer, Float, Boolean, custom types), the $ conversion happens implicitly inside ${} interpolation.\n\nEXAMPLES\n  name <- \"Steve\"\n  stdout.println(`Hello ${name}`)       String, no $ needed\n  age <- 25\n  stdout.println(`Age: ${$age}`)        Integer, $ converts\n  stdout.println(`Age: ${age}`)         also works, implicit conversion\n\nDOUBLE-QUOTED STRINGS\nDouble-quoted strings like \"Hello ${name}\" do NOT support interpolation. The ${...} is treated as literal text. Always use backtick strings for interpolation.\n\nSee Q908 for $ as standalone operator. See Q242 for all conversion operators. See Q897 for operator overview.","ek9Example":"defines module qa.operators.dollarbacktick\n\n  defines function\n\n    formatPerson() as pure\n      ->\n        name as String\n        age as Integer\n      <- rtn as String: String()\n\n      // String variable: ${name} works directly\n      // Integer variable: ${age} uses $ to convert\n      rtn: `${name} is ${age} years old`\n\n  defines program\n\n    DollarBacktickDemo()\n      stdout <- Stdout()\n\n      // === String inside backtick — no $ needed ===\n\n      name <- \"Steve\"\n      stdout.println(`Hello ${name}`)\n\n      // === Integer inside backtick — $ converts ===\n\n      age <- 25\n      stdout.println(`Age: ${age}`)\n\n      // === Float inside backtick ===\n\n      price <- 19.99\n      stdout.println(`Price: ${price}`)\n\n      // === Boolean inside backtick ===\n\n      isEnabled <- true\n      stdout.println(`Active: ${isEnabled}`)\n\n      // === Multiple values ===\n\n      stdout.println(formatPerson(\"Alice\", 30))\n\n      // === Expression inside backtick ===\n\n      x <- 10\n      y <- 20\n      stdout.println(`Sum: ${(x + y)}`)","migrationContext":"Java: String.format() or + concatenation, no interpolation. Python: f-strings with {variable}. JavaScript: template literals with ${expression}. Kotlin: \"$variable\" or \"${expression}\". Ruby: \"#{expression}\". EK9: backtick strings with ${expression}, $ operator for explicit conversion, double-quoted strings have NO interpolation.","keywords":["backtick","conversion","dollar","expression","interpolation","string","template"],"primaryTopics":["backtick interpolation","${} syntax","$ inside interpolation"],"typicalErrors":[{"error":"E08090","correct":"      stdout.println(`Age: ${age}`)","incorrect":"      stdout.println(\"Age: ${age}\")","explanation":"Double-quoted strings do NOT interpolate, so '${age}' is literal text and 'age' becomes unreferenced; use backtick strings for interpolation. See ek9 -h E08090 for details."}],"companions":[]}
{"id":910,"category":"Operators and Expressions","question":"How do I implement the $ operator on a custom class in EK9?","url":"https://ek9.io/qa/QA0910.html","alternatePhrasings":["How do I define _string() for my type in EK9?","How does a class customize its String representation in EK9?","How do I override the $ operator in EK9?"],"answer":"Any EK9 class can define the $ operator to control how it converts to a String. The operator must be declared 'as pure', take no parameters, and return a String.\n\nBASIC PATTERN\n  MyClass\n    name as String: String()\n\n    operator $ as pure\n      <- rtn as String: name\n\nThe $ operator returns whatever String representation makes sense for the type.\n\nRULES FOR operator $\n1. Must be declared 'as pure' — it extracts information without side effects\n2. Must return String — the return type is always String\n3. Takes no parameters — it operates on 'this' only\n4. Can use backtick interpolation to build complex strings\n\nUSING BACKTICK IN operator $\nYou can use backtick interpolation inside the operator to combine fields:\n  operator $ as pure\n    <- rtn as String: `${name} (${$score})`\n\nIMPLICIT USAGE\nWhen you write `${myObject}` in a backtick string, the compiler calls the $ operator automatically. When you write $myObject as a standalone expression, it also calls the $ operator.\n\nDEFAULT OPERATOR\nIf you use 'default operator' on a class, EK9 generates a default $ operator that combines all fields. You can override it with your own implementation.\n\nSee Q908 for $ basics. See Q242 for all conversion operators. See Q245 for complete custom type examples.","ek9Example":"defines module qa.operators.dollarcustom\n\n  defines class\n\n    Colour\n      red as Integer: 0\n      green as Integer: 0\n      blue as Integer: 0\n\n      Colour() as pure\n        ->\n          r as Integer\n          g as Integer\n          b as Integer\n        this.red :=: r\n        this.green :=: g\n        this.blue :=: b\n\n      operator $ as pure\n        <- rtn as String: `rgb(${red}, ${green}, ${blue})`\n\n      override operator ? as pure\n        <- rtn as Boolean: red? and green? and blue?\n\n    NamedColour\n      colourName as String: String()\n      colour as Colour: Colour()\n\n      NamedColour()\n        ->\n          colourName as String\n          colour as Colour\n        this.colourName: colourName\n        this.colour: colour\n\n      operator $ as pure\n        <- rtn as String: `${colourName}: ${colour}`\n\n      override operator ? as pure\n        <- rtn as Boolean: colourName? and colour?\n\n  defines program\n\n    CustomDollarDemo()\n      stdout <- Stdout()\n\n      // === Custom $ on Colour ===\n\n      red <- Colour(255, 0, 0)\n      stdout.println($red)\n\n      // === Custom $ on NamedColour (delegates to Colour.$) ===\n\n      namedRed <- NamedColour(\"Red\", red)\n      stdout.println($namedRed)\n\n      // === $ inside backtick interpolation ===\n\n      blue <- Colour(0, 0, 255)\n      stdout.println(`Blue is: ${blue}`)\n\n      // === Multiple custom types ===\n\n      green <- Colour(0, 255, 0)\n      namedGreen <- NamedColour(\"Green\", green)\n      stdout.println(`Colour: ${namedGreen}`)","migrationContext":"Java: override toString() method. Python: define __str__() or __repr__(). Rust: implement Display trait. Go: define String() method. Kotlin: override toString(). JavaScript: define toString() method. EK9: define 'operator $ as pure' returning String.","keywords":["class","conversion","custom","dollar","operator","override","pure","string"],"primaryTopics":["operator $","custom string conversion","class operators"],"typicalErrors":[{"error":"E07500","correct":"operator $ as pure","incorrect":"operator $","explanation":"The $ operator must be declared 'as pure' because it extracts information without side effects. Omitting 'as pure' triggers E07500. See ek9 -h E07500 for details."}],"companions":[],"oracleToolHint":{"tool":"ek9_add_member","intent":"operator","description":"Oracle can add the $ (string) operator to an existing class with the correct pure signature."}}
{"id":911,"category":"Operators and Expressions","question":"What does the #? operator mean in EK9?","url":"https://ek9.io/qa/QA0911.html","alternatePhrasings":["Is # a comment in EK9?","How does hashcode work in EK9?","What does #? return in EK9?"],"answer":"The #? operator in EK9 calls _hashcode() and returns an Integer hash value. It is NOT a Python-style comment and NOT a Ruby-style method reference.\n\n#? IS _hashcode()\nThe #? operator is read as 'hash-query' and calls the _hashcode() method:\n  hashVal <- #? myObject\nThis returns an Integer suitable for use as a Dict key or equality checking.\n\nIMPORTANT DISTINCTIONS\n- In Python, # starts a comment. In EK9, // starts a comment.\n- In Ruby, #method is an unbound method reference. EK9 has no such syntax.\n- In EK9, # is part of TWO operator symbols: #? (hashcode) and #^ (promote).\n- The # character has NO meaning on its own in EK9.\n\nRETURN TYPE\n#? always returns Integer. The method signature is:\n  operator #? as pure\n    <- rtn as Integer: ...\n\nNOTE THE LOWERCASE 'c'\nThe method is _hashcode (lowercase c), NOT _hashCode (Java style camelCase). This is a common mistake.\n\nIMPLEMENTING #? ON CUSTOM TYPES\n  operator #? as pure\n    <- rtn as Integer: #?field1 + #?field2\nCombine hash values of fields using arithmetic.\n\nDEFAULT OPERATOR\nUsing 'default operator' generates a #? implementation that combines all fields.\n\nSee Q897 for $ and #? together. See Q912 for the #^ promote operator. See Q242 for all conversion operators.","ek9Example":"defines module qa.operators.hashqhashcode\n\n  defines record\n\n    Tag\n      name as String: String()\n      priority as Integer: 0\n\n      Tag()\n        ->\n          name as String\n          priority as Integer\n        this.name :=: name\n        this.priority :=: priority\n\n      default operator\n\n  defines program\n\n    HashcodeDemo()\n      stdout <- Stdout()\n\n      // === #? on String ===\n\n      greeting <- \"Hello\"\n      greetingHash <- #? greeting\n      stdout.println(`String hash: ${greetingHash}`)\n\n      // === #? on Integer ===\n\n      number <- 42\n      numberHash <- #? number\n      stdout.println(`Integer hash: ${numberHash}`)\n\n      // === #? on Float ===\n\n      price <- 19.99\n      priceHash <- #? price\n      stdout.println(`Float hash: ${priceHash}`)\n\n      // === #? on custom record ===\n\n      versionTag <- Tag(\"version\", 1)\n      versionHash <- #? versionTag\n      stdout.println(`Tag hash: ${versionHash}`)\n\n      // === Different values produce different hashes ===\n\n      buildTag <- Tag(\"build\", 2)\n      buildHash <- #? buildTag\n      stdout.println(`Build hash: ${buildHash}`)","migrationContext":"Java: hashCode() method (camelCase). Python: hash() built-in, # is comment. Ruby: hash method, # is interpolation in strings. Rust: Hash trait derive. Go: no built-in hash interface. Kotlin: hashCode() like Java. EK9: #? operator calls _hashcode() (lowercase c), returns Integer.","keywords":["comment","dict","equality","hash","hashcode","integer","key","operator","python"],"primaryTopics":["#? operator","_hashcode method","hash values"],"typicalErrors":[{"error":"E07550","correct":"      default operator","incorrect":"      operator #? as pure\n        <- rtn as String: name","explanation":"The #? operator must return Integer, not String. Defining #? to return String triggers E07550 because hashcode must always return an Integer value. See ek9 -h E07550 for details."}],"companions":[]}
{"id":912,"category":"Operators and Expressions","question":"What does the #^ operator mean in EK9?","url":"https://ek9.io/qa/QA0912.html","alternatePhrasings":["How does type promotion work in EK9?","What is the promote operator in EK9?","How do I widen a type in EK9?"],"answer":"The #^ operator in EK9 calls _promote() for type widening. It converts a value to a wider or more general type. It is NOT list comprehension and NOT reflection.\n\n#^ IS _promote()\nThe #^ operator promotes a value to a wider type:\n  anInteger <- 42\n  asFloat <- #^ anInteger\nThis converts Integer 42 to Float 42.0 via the _promote() method.\n\nIMPORTANT DISTINCTIONS\n- In Python, [x**2 for x in list] is list comprehension. EK9 has NO list comprehension.\n- In some languages, ^ is XOR or power. In EK9, #^ is specifically the promote operator.\n- The # and ^ characters together form this specific operator. Neither has meaning alone as an operator.\n\nTYPE WIDENING EXAMPLES\n- Integer to Float (narrower numeric to wider numeric)\n- Character to String (single char to string)\n- Any type with a defined #^ operator to its target type\n\nRETURN TYPE RULE\nThe #^ operator MUST return a DIFFERENT type from the source. If you define #^ on a class, the return type cannot be the same class. This is enforced by the compiler.\n\nIMPLEMENTING #^ ON CUSTOM TYPES\n  operator #^ as pure\n    <- rtn as Float: Float(internalValue)\nMust be pure, takes no parameters, returns the wider type.\n\nAUTOMATIC PROMOTION\nThe compiler uses #^ automatically when a narrower type is used where a wider type is expected, such as passing an Integer to a function expecting Float.\n\nSee Q911 for the #? hashcode operator. See Q839 for promote return type rules. See Q242 for all conversion operators.","ek9Example":"defines module qa.operators.hashcaretpromote\n\n  defines class\n\n    Measurement\n      amount as Integer: 0\n\n      Measurement() as pure\n        -> amount as Integer\n        this.amount :=: amount\n\n      operator #^ as pure\n        <- rtn as Float: Float(amount)\n\n      operator $ as pure\n        <- rtn as String: `Measurement(${amount})`\n\n      override operator ? as pure\n        <- rtn as Boolean: amount?\n\n  defines program\n\n    PromoteDemo()\n      stdout <- Stdout()\n\n      // === #^ on Integer — promotes to Float ===\n\n      wholeNumber <- 42\n      promoted <- #^ wholeNumber\n      stdout.println(`Integer ${wholeNumber} promoted to Float: ${promoted}`)\n\n      // === #^ on custom class ===\n\n      measurement <- Measurement(100)\n      asFloat <- #^ measurement\n      stdout.println(`Measurement promoted: ${asFloat}`)\n\n      // === Promotion preserves value ===\n\n      small <- 1\n      smallFloat <- #^ small\n      stdout.println(`Small promoted: ${smallFloat}`)\n\n      large <- 999999\n      largeFloat <- #^ large\n      stdout.println(`Large promoted: ${largeFloat}`)","migrationContext":"Java: implicit widening (int to double). Python: implicit numeric promotion. Rust: explicit 'as' casting (i32 as f64). Go: explicit conversion float64(intVal). Kotlin: toDouble() method. JavaScript: implicit coercion. EK9: #^ operator calls _promote(), explicit and type-safe, must return different type.","keywords":["caret","conversion","float","hash","integer","operator","promote","type","widening"],"primaryTopics":["#^ operator","_promote method","type widening"],"typicalErrors":[{"error":"E07420","correct":"operator #^ as pure\n        <- rtn as Float: Float(amount)","incorrect":"operator #^ as pure\n        <- rtn as Measurement: Measurement(amount)","explanation":"The #^ promote operator must return a DIFFERENT type from the class it is defined on. Returning the same type (Measurement) defeats the purpose of promotion and triggers E07420. See ek9 -h E07420 for details."}],"companions":[]}
{"id":913,"category":"Operators and Expressions","question":"How does $ work on different types in EK9?","url":"https://ek9.io/qa/QA0913.html","alternatePhrasings":["Can I use $ on any type in EK9?","What does $ return for Integer, Float, Boolean?","Does $ always call _string()?"],"answer":"The $ operator calls _string() on ANY type that implements it. Every built-in type has a $ operator, and custom classes can define their own. The result is always a String.\n\n$ ON BUILT-IN TYPES\n  $25           returns \"25\" (Integer to String)\n  $19.99        returns \"19.99\" (Float to String)\n  $true         returns \"true\" (Boolean to String)\n  $\"hello\"      returns \"hello\" (String to String, identity)\n  $'A'          returns \"A\" (Character to String)\n\n$ ON COLLECTIONS\nList and other collections implement $ to produce a string representation:\n  names <- [\"Alice\", \"Bob\"]\n  namesText <- $names\nThe exact format depends on the type's _string() implementation.\n\n$ ON CUSTOM CLASSES\nAny class with 'operator $ as pure' can be converted:\n  colour <- Colour(255, 0, 0)\n  colourText <- $colour     calls Colour's $ operator\n\nCONSISTENCY\n$ ALWAYS means _string(). There are no exceptions, no special cases, no context-dependent behavior. It is the single, universal way to get a String representation of any value.\n\nIN BACKTICK STRINGS\nInside backtick interpolation ${...}, the $ conversion is called implicitly for non-String types. But as a standalone prefix operator, $ must be written explicitly.\n\nSee Q908 for $ basics. See Q909 for $ in backtick strings. See Q910 for implementing $ on custom types.","ek9Example":"defines module qa.operators.dollarvaried\n\n  defines class\n\n    Score\n      points as Integer: 0\n      label as String: String()\n\n      Score() as pure\n        ->\n          points as Integer\n          label as String\n        this.points :=: points\n        this.label :=: label\n\n      operator $ as pure\n        <- rtn as String: `${label}=${points}`\n\n      override operator ? as pure\n        <- rtn as Boolean: points? and label?\n\n  defines program\n\n    DollarVariedDemo()\n      stdout <- Stdout()\n\n      // === $ on Integer ===\n\n      count <- 42\n      countText <- $count\n      stdout.println(`Integer: ${countText}`)\n\n      // === $ on Float ===\n\n      ratio <- 3.14\n      ratioText <- $ratio\n      stdout.println(`Float: ${ratioText}`)\n\n      // === $ on Boolean ===\n\n      isActive <- true\n      activeText <- $isActive\n      stdout.println(`Boolean: ${activeText}`)\n\n      // === $ on String (identity) ===\n\n      name <- \"Steve\"\n      nameText <- $name\n      stdout.println(`String: ${nameText}`)\n\n      // === $ on Character ===\n\n      letter <- 'A'\n      letterText <- $letter\n      stdout.println(`Character: ${letterText}`)\n\n      // === $ on custom class ===\n\n      score <- Score(95, \"Math\")\n      scoreText <- $score\n      stdout.println(`Custom: ${scoreText}`)\n\n      // === $ on List ===\n\n      items <- [\"one\", \"two\", \"three\"]\n      itemsText <- $items\n      stdout.println(`List: ${itemsText}`)","migrationContext":"Java: toString() on all objects. Python: str() works on all types. Rust: Display trait must be implemented. Go: Stringer interface optional. JavaScript: String() or toString(). EK9: $ operator universal, calls _string(), every built-in type supports it.","keywords":["boolean","conversion","dollar","float","integer","list","string","types","universal"],"primaryTopics":["$ on various types","universal string conversion"],"typicalErrors":[{"error":"E50060","correct":"countText <- $count","incorrect":"countText <- count.toString()","explanation":"EK9 does not have a toString() method like Java. Use the $ prefix operator ($count) to convert any value to String. The $ calls the _string() method. See ek9 -h E50060 for details."}],"companions":[]}
{"id":914,"category":"Operators and Expressions","question":"Does # mean comment in EK9 like Python?","url":"https://ek9.io/qa/QA0914.html","alternatePhrasings":["Is # a comment character in EK9?","What do #? and #^ mean in EK9?","How is # different in EK9 versus Python?"],"answer":"No. In EK9, # is NOT a comment character. EK9 uses // for single-line comments. The # character appears ONLY in two specific operator symbols: #? and #^. That is it.\n\n#? = HASHCODE\nThe #? operator calls _hashcode() and returns an Integer:\n  hash <- #? value\nThis is analogous to Java's hashCode() but uses operator syntax.\n\n#^ = PROMOTE\nThe #^ operator calls _promote() for type widening:\n  wider <- #^ narrowValue\nThis converts to a wider type (e.g., Integer to Float).\n\nTHAT IS THE COMPLETE LIST\nThere are exactly TWO operators using #:\n- #? (hashcode) — returns Integer\n- #^ (promote) — returns a wider type\n\nThere are also #< (prefix) and #> (suffix) operators:\n- #< extracts a prefix (e.g., first character of String)\n- #> extracts a suffix (e.g., last character of String)\n\nNO OTHER # OPERATORS EXIST\nThese do NOT exist in EK9:\n- # alone (not a comment, not an operator)\n- ## (not an operator)\n- #! (only valid on first line as shebang)\n- #$ (not an operator)\n- #@ (not an operator)\n\nCOMMENTS IN EK9\n// This is a comment in EK9\n/* This is also a comment in EK9 */\n\nSee Q911 for #? in detail. See Q912 for #^ in detail. See Q903 for all operators that do not exist.","ek9Example":"defines module qa.operators.hashnotpython\n\n  defines record\n\n    Point\n      xPos as Float: 0.0\n      yPos as Float: 0.0\n\n      Point()\n        ->\n          x as Float\n          y as Float\n        this.xPos :=: x\n        this.yPos :=: y\n\n      default operator\n\n  defines program\n\n    HashOperatorsDemo()\n      stdout <- Stdout()\n\n      // === #? on various types ===\n\n      name <- \"Hello\"\n      nameHash <- #? name\n      stdout.println(`String hash: ${nameHash}`)\n\n      number <- 42\n      numberHash <- #? number\n      stdout.println(`Integer hash: ${numberHash}`)\n\n      point <- Point(3.0, 4.0)\n      pointHash <- #? point\n      stdout.println(`Point hash: ${pointHash}`)\n\n      // === #^ promote ===\n\n      intVal <- 100\n      floatVal <- #^ intVal\n      stdout.println(`Promoted: ${floatVal}`)\n\n      // === #< prefix and #> suffix ===\n\n      text <- \"Hello\"\n      first <- #< text\n      last <- #> text\n      stdout.println(`Prefix: ${first}`)\n      stdout.println(`Suffix: ${last}`)\n\n      // === Comments use // not # ===\n      // This is a valid EK9 comment\n\n      stdout.println(\"# is NOT a comment in EK9\")","migrationContext":"Python: # is line comment. Ruby: # is line comment, #{ } is string interpolation. Shell: # is line comment. EK9: # is NOT a comment, // is line comment, #? is hashcode operator, #^ is promote operator, #< is prefix, #> is suffix.","keywords":["comment","hash","hashcode","not-comment","operator","prefix","promote","python","suffix"],"primaryTopics":["# operators","not Python comment","#? and #^ only"],"typicalErrors":[{"error":"E07620","correct":"pointHash <- #? point","incorrect":"pointHash <- #^ point","explanation":"The #^ (promote) operator is not defined on the Point record. Point has #? (hashcode) via 'default operator' but not #^. Use #? for hashcode. See ek9 -h E07620 for details."}],"companions":[]}
{"id":915,"category":"What AI Gets Wrong About EK9","question":"Does EK9 have list comprehension like Python?","url":"https://ek9.io/qa/QA0915.html","alternatePhrasings":["How do I use [x for x in list] in EK9?","What replaces list comprehension in EK9?","How do I filter and transform lists in EK9?"],"answer":"EK9 has NO list comprehension syntax. Python's [x for x in list if condition] does NOT exist in EK9. AI models trained on Python frequently generate this pattern. It will not compile.\n\nTHE AI MISTAKE\nAI generates patterns like:\n  result <- [item * 2 for item in items]\n  filtered <- [x for x in data if x > 0]\nNone of these compile. EK9 has no square bracket comprehension syntax.\n\nTHE EK9 WAY: STREAM PIPELINES\nEK9 uses stream pipelines with cat, filter, map, and collect:\n\nFILTERING\n  for item in items\n    if item?\n      process(item)\n\nTRANSFORMING\nUse functions to transform values and build new collections:\n  doubled <- List() of Integer\n  for item in items\n    doubled += item * 2\n\nFUNCTIONAL APPROACH\nEK9 supports higher-order functions for transformation:\n  doubleValue() as pure\n    -> value as Integer\n    <- rtn as Integer: value * 2\n\nWHY NO COMPREHENSION\nList comprehension is syntactic sugar that mixes iteration, filtering, and transformation into one dense expression. EK9 prefers explicit, readable steps. Each operation is visible and debuggable.\n\nCOLLECTION BUILDING\nBuild collections explicitly:\n  result <- List() of String\n  for name in names\n    if name?\n      result += name\n\nSee Q274 for return statement mistakes. See Q275 for break/continue mistakes. See Q281 for verifying AI code.","ek9Example":"defines module qa.aimistakes.nocomprehension\n\n  defines function\n\n    doubleNumber() as pure\n      -> number as Integer\n      <- rtn as Integer: number * 2\n\n    isPositive() as pure\n      -> number as Integer\n      <- rtn as Boolean: number > 0\n\n  defines program\n\n    NoComprehensionDemo()\n      stdout <- Stdout()\n\n      // === Building a filtered list (replaces list comprehension) ===\n\n      numbers <- [1, -2, 3, -4, 5]\n\n      positives <- List() of Integer\n      for num in numbers\n        if isPositive(num)\n          positives += num\n\n      stdout.println(`Positives: ${positives}`)\n\n      // === Building a transformed list ===\n\n      doubled <- List() of Integer\n      for num in numbers\n        doubled += doubleNumber(num)\n\n      stdout.println(`Doubled: ${doubled}`)\n\n      // === Combined filter and transform ===\n\n      doubledPositives <- List() of Integer\n      for num in numbers\n        if isPositive(num)\n          doubledPositives += doubleNumber(num)\n\n      stdout.println(`Doubled positives: ${doubledPositives}`)\n\n      // === Simple collection from range ===\n\n      squares <- List() of Integer\n      for i in 1 ... 5\n        squares += i * i\n\n      stdout.println(`Squares: ${squares}`)","migrationContext":"Python: [x for x in list if cond] comprehension. Haskell: [x | x <- list, cond] comprehension. Scala: for yield comprehension. Kotlin: list.filter{}.map{}. JavaScript: array.filter().map(). EK9: NO comprehension syntax, use for loops with explicit collection building or stream pipelines.","keywords":["ai","comprehension","filter","list","map","mistake","pipeline","python","stream","transform","wrong"],"primaryTopics":["no list comprehension","stream pipelines","collection building"],"typicalErrors":[{"error":"E01081","correct":"doubled <- List() of Integer\n      for num in numbers\n        doubled += doubleNumber(num)","incorrect":"doubled <- [item * 2 for item in items]","explanation":"EK9 has NO list comprehension. Use explicit for loops to build collections, or use stream pipelines for functional-style processing. See ek9 -h E01081 for details."}],"companions":[]}
{"id":916,"category":"Syntax and Structure Rules","question":"How do I declare multiple parameters on a function in EK9?","url":"https://ek9.io/qa/QA0916.html","alternatePhrasings":["Can I put all parameters on one line in EK9?","What does the -> arrow mean for parameters in EK9?","How do I use the parameter arrow in EK9?"],"answer":"EK9 uses ONE -> arrow followed by indented parameters below it. Parameters are NOT comma-separated on one line.\n\nCORRECT SYNTAX\nOne -> arrow, then each parameter on its own indented line:\n  greet()\n    ->\n      name as String\n      age as Integer\n    <- rtn as String: `${name} is ${$age}`\n\nSINGLE PARAMETER SHORTHAND\nFor a single parameter, you can put it on the same line as ->:\n  greet()\n    -> name as String\n    <- rtn as String: `Hello ${name}`\n\nWHAT NOT TO DO\n- Do NOT use commas between parameters\n- Do NOT put multiple parameters on one line\n- Do NOT use multiple -> arrows\n- Do NOT use parentheses around parameters\n\nWRONG PATTERNS (do not compile)\n  greet(name as String, age as Integer)    WRONG: not C-style\n  greet()\n    -> name as String, age as Integer      WRONG: no commas\n  greet()\n    -> name as String\n    -> age as Integer                      WRONG: only one ->\n\nCONSTRUCTOR PARAMETERS\nConstructors follow the same rule:\n  MyClass()\n    ->\n      name as String\n      score as Integer\n    this.name :=: name\n    this.score :=: score\n\nSee Q894 for constructor parameter syntax. See Q893 for section headers. See Q907 for EK9 structure overview.","ek9Example":"defines module qa.syntaxrules.arrowparams\n\n  defines function\n\n    // Single parameter — shorthand on same line as ->\n    greetSimple() as pure\n      -> name as String\n      <- rtn as String: `Hello ${name}`\n\n    // Multiple parameters — -> then indented below\n    greetPerson() as pure\n      ->\n        name as String\n        age as Integer\n      <- rtn as String: `${name} is ${age}`\n\n    // Three parameters\n    formatEntry() as pure\n      ->\n        key as String\n        setting as String\n        index as Integer\n      <- rtn as String: `${index}: ${key}=${setting}`\n\n  defines program\n\n    ParamArrowDemo()\n      stdout <- Stdout()\n\n      // === Single parameter ===\n\n      stdout.println(greetSimple(\"Steve\"))\n\n      // === Multiple parameters ===\n\n      stdout.println(greetPerson(\"Alice\", 30))\n\n      // === Three parameters ===\n\n      stdout.println(formatEntry(\"userName\", \"Bob\", 1))\n      stdout.println(formatEntry(\"userRole\", \"dev\", 2))","migrationContext":"Java: method(String name, int age) comma-separated in parentheses. Python: def method(name, age) comma-separated. Rust: fn method(name: String, age: i32) comma-separated. Go: func method(name string, age int) comma-separated. EK9: ONE -> arrow then indented parameters, each on its own line.","keywords":["arrow","declaration","function","indent","multiple","parameter","syntax"],"primaryTopics":["-> parameter syntax","multiple parameters","indentation"],"typicalErrors":[{"error":"E01082","correct":"    greetPerson() as pure\n      ->\n        name as String\n        age as Integer","incorrect":"    greetPerson() as pure\n      -> name as String\n      -> age as Integer","explanation":"EK9 does not allow multiple -> arrows. Use ONE -> arrow then indent each parameter on its own line below. Multiple -> arrows trigger E01082. See ek9 -h E01082 for details."}],"companions":[]}
{"id":917,"category":"Syntax and Structure Rules","question":"How do return values work in EK9 functions?","url":"https://ek9.io/qa/QA0917.html","alternatePhrasings":["Can I have multiple return statements in EK9?","How do I declare a return value in EK9?","What does <- mean for returns in EK9?"],"answer":"EK9 uses ONE <- return declaration per function. There is NO return keyword. You declare a named return variable, give it a default value, then update it on different code paths.\n\nBASIC PATTERN\n  classify() as pure\n    -> score as Integer\n    <- label as String: \"average\"\n    if score >= 90\n      label: \"excellent\"\n    else if score < 40\n      label: \"poor\"\n\nThe variable 'label' is declared with <- and default \"average\". Different code paths update it with the : assignment operator. The compiler ensures all paths initialise the variable.\n\nRULES\n1. ONE <- per function (not multiple returns)\n2. The return variable MUST have a default value or be initialised on ALL paths\n3. Use : to update the return variable on conditional paths\n4. There is NO return keyword — the named variable is automatically returned\n\nNO EARLY RETURN\nInstead of returning early, structure your logic so the return variable gets the right value:\n  process() as pure\n    -> text as String\n    <- rtn as String: \"empty\"\n    if text?\n      rtn: text.upperCase()\nIf text is unset, rtn stays \"empty\". If set, rtn gets the uppercase version.\n\nMULTIPLE RETURN VALUES\nIf you need multiple return values, return a record or class:\n  splitName() as pure\n    -> fullName as String\n    <- parts as NameParts: NameParts()\n\nWRONG PATTERNS\n  <- a as String: \"x\"\n  <- b as String: \"y\"       WRONG: only one <- allowed\n  return \"hello\"             WRONG: no return keyword\n\nSee Q274 for why return was removed. See Q916 for parameter syntax. See Q50 for declared return variables.","ek9Example":"defines module qa.syntaxrules.onereturn\n\n  defines function\n\n    // Simple return with default\n    greet() as pure\n      -> name as String\n      <- message as String: \"Hello, \" + name\n\n    // Conditional paths update the return variable\n    classify() as pure\n      -> score as Integer\n      <- label as String: \"average\"\n\n      excellentThreshold <- 90\n      poorThreshold <- 40\n\n      if score >= excellentThreshold\n        label: \"excellent\"\n      else if score < poorThreshold\n        label: \"poor\"\n\n    // Guard pattern instead of early return\n    safeUpperCase() as pure\n      -> text as String\n      <- rtn as String: \"empty\"\n\n      if text?\n        rtn: text.upperCase()\n\n  defines program\n\n    OneReturnDemo()\n      stdout <- Stdout()\n\n      // === Simple return ===\n\n      stdout.println(greet(\"Steve\"))\n\n      // === Conditional return ===\n\n      stdout.println(`Score 95: ${classify(95)}`)\n      stdout.println(`Score 30: ${classify(30)}`)\n      stdout.println(`Score 60: ${classify(60)}`)\n\n      // === Guard pattern ===\n\n      stdout.println(`Upper: ${safeUpperCase(\"hello\")}`)\n      stdout.println(`Empty: ${safeUpperCase(String())}`)","migrationContext":"Java: return statement, multiple returns common. Python: return statement, multiple returns common. Rust: implicit last expression or return. Go: named returns exist but return still needed. Kotlin: return statement. EK9: ONE named return variable with <-, NO return keyword, compiler verifies all paths.","keywords":["arrow","declaration","function","named","return","single","value","variable"],"primaryTopics":["<- return declaration","named return variable","no return keyword"],"typicalErrors":[{"error":"E01083","correct":"<- label as String: \"average\"","incorrect":"<- label as String: \"average\"\n      <- extra as Integer: 0","explanation":"EK9 allows only ONE <- return declaration per function. Multiple <- arrows trigger E01083. Declare a single named return variable and update it on different code paths. See ek9 -h E01083 for details."}],"companions":[]}
{"id":918,"category":"Operators and Expressions","question":"Is $ string interpolation or safe access in EK9?","url":"https://ek9.io/qa/QA0918.html","alternatePhrasings":["Does $ do interpolation in EK9?","Is $ a safe access operator in EK9?","What does $ NOT do in EK9?"],"answer":"No. $ does NOT do string interpolation. $ does NOT do safe access. $ converts ANY value to String by calling _string(). That is the ONLY thing it does.\n\nWHAT $ DOES\n$ is a PREFIX operator that calls _string() on any value and returns a String:\n  age <- 25\n  ageText <- $age         calls Integer._string(), returns \"25\"\n  price <- 19.99\n  priceText <- $price     calls Float._string(), returns \"19.99\"\n  flag <- true\n  flagText <- $flag       calls Boolean._string(), returns \"true\"\n\nWHAT $ DOES NOT DO\n1. NOT string interpolation — that is backtick ${...}\n2. NOT safe access — use ? suffix for isSet checks\n3. NOT variable sigil — EK9 variables have no sigils\n4. NOT template literal marker — backtick strings handle that\n5. NOT currency symbol — it is an operator\n\nCOMMON CONFUSION WITH BACKTICK STRINGS\nInside backtick strings:\n  `Hello ${name}`      ${...} is interpolation, not $ operator\n  `Age: ${$age}`       inner $ is the operator, outer ${} is interpolation\n\nStandalone:\n  text <- $value         this IS the $ operator (conversion)\n  msg <- \"${value}\"     this does NOT interpolate (double-quoted string)\n\nSAFE ACCESS USES ?\nFor checking if a value is set, use the ? suffix operator:\n  if value?\n    process(value)\n$ has nothing to do with null safety or set checking.\n\nSee Q908 for $ basics. See Q909 for $ in backtick strings. See Q898 for ? isSet operator.","ek9Example":"defines module qa.operators.dollarnotinterp\n\n  defines function\n\n    // $ converts to String, nothing else\n    convertAll() as pure\n      ->\n        number as Integer\n        ratio as Float\n        isEnabled as Boolean\n      <- rtn as String: String()\n\n      numText <- $number\n      ratioText <- $ratio\n      enabledText <- $isEnabled\n      rtn: `${numText}, ${ratioText}, ${enabledText}`\n\n  defines program\n\n    DollarNotInterpDemo()\n      stdout <- Stdout()\n\n      // === $ is _string() conversion ===\n\n      age <- 25\n      ageText <- $age\n      stdout.println(`Age text: ${ageText}`)\n\n      // === $ is NOT interpolation ===\n      // Interpolation uses backtick ${...}\n\n      name <- \"Steve\"\n      stdout.println(`Hello ${name}`)\n\n      // === $ inside backtick is conversion ===\n\n      score <- 100\n      stdout.println(`Score: ${score}`)\n\n      // === ? is isSet, NOT $ ===\n\n      count <- 42\n      stdout.println(`Is set: ${count?}`)\n\n      // === Multiple conversions ===\n\n      stdout.println(convertAll(10, 3.14, true))\n\n      // === $ on already-String is identity ===\n\n      greeting <- \"World\"\n      greetText <- $greeting\n      stdout.println(`Greeting: ${greetText}`)","migrationContext":"JavaScript: $ in template literals ${expr}. Kotlin: $variable in strings for interpolation. PHP: $variable as variable sigil. Perl: $scalar as variable sigil. Shell: $VAR for variable expansion. EK9: $ is ONLY a prefix operator calling _string(), never interpolation, never variable sigil, never safe access.","keywords":["access","conversion","dollar","interpolation","misconception","not","operator","prefix","safe","string"],"primaryTopics":["$ is not interpolation","$ is not safe access","$ is _string() only"],"typicalErrors":[{"error":"E08090","correct":"ageText <- $age","incorrect":"ageText <- \"Age: ${age}\"","explanation":"Double-quoted strings do NOT support interpolation — '${age}' is literal text, so the 'age' variable becomes unreferenced. Use backtick strings for interpolation or $ as a prefix operator. See ek9 -h E08090 for details."}],"companions":[]}
{"id":919,"category":"Operators and Expressions","question":"What is the complete # operator family in EK9?","url":"https://ek9.io/qa/QA0919.html","alternatePhrasings":["What # operators exist in EK9?","Are there operators like ##, #!, #$ in EK9?","What are all the hash operators in EK9?"],"answer":"EK9 has exactly FOUR operators that use the # character. No others exist.\n\nTHE COMPLETE # OPERATOR LIST\n\n#? — HASHCODE\nCalls _hashcode(), returns Integer:\n  hash <- #? myObject\nUsed for Dict keys and equality support.\n\n#^ — PROMOTE\nCalls _promote(), returns a WIDER type:\n  asFloat <- #^ anInteger\nConverts a value to a more general type.\n\n#< — PREFIX\nCalls _prefix(), returns the opening part:\n  first <- #< text\nOn String, returns the first character.\n\n#> — SUFFIX\nCalls _suffix(), returns the closing part:\n  last <- #> text\nOn String, returns the last character.\n\nOPERATORS THAT DO NOT EXIST\nThese are NOT valid EK9 operators:\n- # alone (not a comment, not an operator)\n- ## (does not exist)\n- #! (only valid as shebang on line 1)\n- #$ (does not exist)\n- #@ (does not exist)\n- #: (does not exist)\n- #{ (does not exist)\n\nSUMMARY TABLE\n  #?  _hashcode()  -> Integer     hash value for Dict keys\n  #^  _promote()   -> wider type  type widening/promotion\n  #<  _prefix()    -> varies      extract prefix/first part\n  #>  _suffix()    -> varies      extract suffix/last part\n\nAll four must be declared 'as pure' because they extract information without side effects.\n\nSee Q911 for #? in detail. See Q912 for #^ in detail. See Q242 for all conversion operators.","ek9Example":"defines module qa.operators.hashsummary\n\n  defines record\n\n    Coord\n      xPos as Float: 0.0\n      yPos as Float: 0.0\n\n      Coord()\n        ->\n          x as Float\n          y as Float\n        this.xPos :=: x\n        this.yPos :=: y\n\n      default operator\n\n  defines program\n\n    HashSummaryDemo()\n      stdout <- Stdout()\n\n      // === #? hashcode — returns Integer ===\n\n      text <- \"Hello\"\n      textHash <- #? text\n      stdout.println(`#? String hash: ${textHash}`)\n\n      number <- 42\n      numHash <- #? number\n      stdout.println(`#? Integer hash: ${numHash}`)\n\n      coord <- Coord(3.0, 4.0)\n      coordHash <- #? coord\n      stdout.println(`#? Record hash: ${coordHash}`)\n\n      // === #^ promote — returns wider type ===\n\n      intVal <- 100\n      floatVal <- #^ intVal\n      stdout.println(`#^ Integer to Float: ${floatVal}`)\n\n      // === #< prefix — returns first part ===\n\n      word <- \"Hello\"\n      first <- #< word\n      stdout.println(`#< prefix: ${first}`)\n\n      // === #> suffix — returns last part ===\n\n      last <- #> word\n      stdout.println(`#> suffix: ${last}`)\n\n      // === All four on one value ===\n\n      sample <- \"World\"\n      stdout.println(`Value: ${sample}`)\n      stdout.println(`  #? hash: ${(#? sample)}`)\n      stdout.println(`  #< prefix: ${(#< sample)}`)\n      stdout.println(`  #> suffix: ${(#> sample)}`)","migrationContext":"Java: hashCode() method, no promote/prefix/suffix operators. Python: hash() function, # is comment. Rust: Hash trait, no prefix/suffix operators. Go: no hash interface, no operator symbols. Kotlin: hashCode() method, no operator symbols. EK9: #? hashcode, #^ promote, #< prefix, #> suffix — four operators, all pure.","keywords":["complete","family","hash","hashcode","operator","prefix","promote","suffix","summary"],"primaryTopics":["# operator family","#? #^ #< #>","complete list"],"typicalErrors":[{"error":"E50060","correct":"numHash <- #? number","incorrect":"numHash <- number.hashCode()","explanation":"EK9 does not have a hashCode() method like Java. Use the #? prefix operator to get the hashcode of any value. The #? calls the _hashcode() method. See ek9 -h E50060 for details."}],"companions":[]}
{"id":920,"category":"Variable Naming Rules and Conventions","question":"What is the complete list of banned variable names in EK9?","url":"https://ek9.io/qa/QA0920.html","alternatePhrasings":["List every banned variable name in EK9","What names trigger E11031?","Show me all forbidden identifiers in EK9"],"answer":"EK9 bans exactly 12 non-descriptive variable names at compile time via E11031: temp, tmp, flag, flg, data, dat, object, obj, value, val, buffer, buf. The check is case-insensitive (Data, DATA, data all rejected).\n\nThese are NOT Java reserved keywords. They are generic, meaningless identifiers that hide intent. Names like str, num, item, result, input, output, count, index, list, map, set, key, status, state, type, config, param, arg, var, ret, res are NOT banned — only the 12 names above trigger E11031.\n\nSeparately, E11032 bans operator keywords as variable names: empty, length, contains, abs, sqrt, close, matches. These shadow operators and cause confusing parse errors.\n\nALWAYS ALLOWED EXCEPTIONS:\n- Single-character names: x, y, z, i, j, k (math variables, loop counters)\n- Compound words containing banned roots: connectionState, errorHandler, dataProcessor\n- Type-qualified descriptive names: orderCount, sensorReading, customerName\n\nSee Q290 for tiers of restriction. See Q906 for a renaming guide.","ek9Example":"defines module qa.naming.completelist\n\n  defines function\n\n    <?-\n      Demonstrates descriptive naming replacing banned names.\n    -?>\n    measureTemperature() as pure\n      -> sensorReading as Float\n      <- adjustedReading as Float: sensorReading + 0.5\n\n  defines program\n\n    CompleteListDemo()\n      stdout <- Stdout()\n\n      // GOOD: descriptive names instead of banned names\n      sensorReading <- 42.5\n      adjustedReading <- measureTemperature(sensorReading)\n      stdout.println(`Reading: ${adjustedReading}`)\n\n      // GOOD: single-character math variables (always allowed)\n      x <- 3.0\n      y <- 4.0\n      stdout.println(`Coordinates: ${x}, ${y}`)\n\n      // GOOD: compound words with banned roots (always allowed)\n      connectionState <- \"active\"\n      dataProcessor <- \"json\"\n      stdout.println(`State: ${connectionState}, Processor: ${dataProcessor}`)","migrationContext":"","keywords":["E11031","banned","complete","forbidden","list","naming","variable"],"primaryTopics":["complete banned name list","E11031"],"typicalErrors":[{"error":"E11031","correct":"      sensorReading <- 42.5\n      adjustedReading <- measureTemperature(sensorReading)","incorrect":"      data <- 42.5\n      adjustedReading <- measureTemperature(data)","explanation":"The name 'data' is one of 12 banned non-descriptive names (E11031). Use a name describing what the variable represents. See ek9 -h E11031."}],"companions":[]}
{"id":921,"category":"Variable Naming Rules and Conventions","question":"Are EK9 banned names the same as Java reserved keywords?","url":"https://ek9.io/qa/QA0921.html","alternatePhrasings":["Is 'data' a keyword in EK9?","Are banned names language keywords?","Why is 'temp' banned if it is not a keyword?"],"answer":"NO. EK9 banned names are descriptive names like temp, value, data — NOT language keywords. Java reserved keywords are things like 'class', 'public', 'void'. EK9 banned names are generic identifiers that carry no semantic meaning.\n\nEK9 banned names are NOT Java reserved keywords. They are quality enforcement — the compiler rejects names that hide intent.\n\nEK9 bans exactly 12 non-descriptive names (E11031): temp, tmp, flag, flg, data, dat, object, obj, value, val, buffer, buf. These are case-insensitive. Names like str, num, item, result, input, output, count, index, list, map, set, key, status, state, type, config, param, arg, var, ret, res are NOT banned.\n\nSeparately, E11032 bans operator keywords as variable names: empty, length, contains, abs, sqrt, close, matches.\n\nALWAYS ALLOWED EXCEPTIONS:\n- Single-character names: x, y, z, i, j, k (math variables, loop counters)\n- Compound words: connectionState, dataProcessor, bufferSize\n- Descriptive names: customerName, orderTotal, retryLimit","ek9Example":"defines module qa.naming.notjava\n\n  defines function\n\n    <?-\n      Shows descriptive naming — banned names are NOT keywords.\n    -?>\n    computeDiscount() as pure\n      ->\n        originalPrice as Float\n        discountRate as Float\n      <- discountedPrice as Float: originalPrice * (1.0 - discountRate)\n\n  defines program\n\n    NotJavaKeywordsDemo()\n      stdout <- Stdout()\n\n      // 'result' is banned — NOT a keyword, just non-descriptive\n      computedScore <- 88\n      stdout.println(`Score: ${computedScore}`)\n\n      // 'value' is banned — use what it represents\n      originalPrice <- 49.99\n      discountedPrice <- computeDiscount(originalPrice, 0.15)\n      stdout.println(`Price: ${discountedPrice}`)\n\n      // Compound words with banned roots: ALLOWED\n      bufferSize <- 1024\n      stateCode <- \"TX\"\n      stdout.println(`Buffer size: ${bufferSize}, State: ${stateCode}`)\n\n      // Single-char math vars: ALLOWED\n      x <- 10.0\n      y <- 20.0\n      stdout.println(`Point: ${x}, ${y}`)","migrationContext":"","keywords":["E11031","banned","java","keyword","naming","reserved"],"primaryTopics":["banned names vs keywords","E11031"],"typicalErrors":[{"error":"E11031","correct":"      computedScore <- 88\n      stdout.println(`Score: ${computedScore}`)","incorrect":"      val <- 88\n      stdout.println(`Score: ${$val}`)","explanation":"The name 'val' is banned (E11031) — it is not a keyword but a generic non-descriptive name. Use a name like 'computedScore' that describes what the value represents."}],"companions":[]}
{"id":922,"category":"Variable Naming Rules and Conventions","question":"Why does my EK9 code fail with E11031 when I use 'data' as a variable name?","url":"https://ek9.io/qa/QA0922.html","alternatePhrasings":["What does E11031 mean?","Why is 'data' rejected by the EK9 compiler?","How do I fix E11031 naming error?"],"answer":"E11031 means you used a banned non-descriptive variable name. The name 'data' tells the reader nothing about what it represents. EK9 enforces naming quality at compile time — this is not a warning, it is an error.\n\nEK9 banned names are NOT Java reserved keywords. They are generic identifiers rejected for quality reasons.\n\nEK9 bans exactly 12 non-descriptive names (E11031): temp, tmp, flag, flg, data, dat, object, obj, value, val, buffer, buf. These are case-insensitive. Names like str, num, item, result, input, output, count, index, list, map, set, key, status, state, type, config, param, arg, var, ret, res are NOT banned.\n\nSeparately, E11032 bans operator keywords as variable names: empty, length, contains, abs, sqrt, close, matches.\n\nFIX: Replace with a descriptive name. Instead of 'data', use 'payload', 'sensorReading', 'configEntry'.\n\nALWAYS ALLOWED EXCEPTIONS:\n- Single-character names: x, y, z, i, j, k (math variables, loop counters)\n- Compound words: dataProcessor, inputStream, configPath\n- Descriptive alternatives: payload, threshold, customerRecord","ek9Example":"defines module qa.naming.whycompileerror\n\n  defines function\n\n    <?-\n      Shows how to fix E11031 by using descriptive names.\n    -?>\n    fetchPayload() as pure\n      <- payload as String: \"sensor-data-packet\"\n\n  defines program\n\n    WhyCompileErrorDemo()\n      stdout <- Stdout()\n\n      // WRONG: data <- fetchPayload()   triggers E11031\n      // FIXED: descriptive name\n      payload <- fetchPayload()\n      stdout.println(`Payload: ${payload}`)\n\n      // WRONG: temp <- 42.5   triggers E11031\n      // FIXED: descriptive name\n      sensorReading <- 42.5\n      stdout.println(`Sensor: ${sensorReading}`)\n\n      // Compound words: ALLOWED (dataProcessor contains 'data' but is compound)\n      dataProcessor <- \"jsonParser\"\n      stdout.println(`Processor: ${dataProcessor}`)\n\n      // Single-char loop counters: ALLOWED\n      i <- 1\n      k <- 10\n      stdout.println(`Range: ${i} to ${k}`)","migrationContext":"","keywords":["E11031","banned","compile","data","error","fix","naming"],"primaryTopics":["E11031 troubleshooting","fixing banned names"],"typicalErrors":[{"error":"E11031","correct":"      payload <- fetchPayload()\n      stdout.println(`Payload: ${payload}`)","incorrect":"      data <- fetchPayload()\n      stdout.println(`Payload: ${data}`)","explanation":"The name 'data' is banned (E11031). Replace with a descriptive name like 'payload' that tells the reader what this variable holds."}],"companions":[]}
{"id":923,"category":"Variable Naming Rules and Conventions","question":"What variable names ARE allowed in EK9 despite the naming rules?","url":"https://ek9.io/qa/QA0923.html","alternatePhrasings":["What exceptions exist to EK9 naming rules?","Can I use single-letter names in EK9?","Are compound words with banned roots allowed?"],"answer":"EK9 allows three categories of names that might seem like they would be banned:\n\n1. SINGLE-CHARACTER NAMES: x, y, z, i, j, k, n, t — always allowed for math variables, loop counters, generic type parameters.\n\n2. COMPOUND WORDS containing banned roots: connectionState, dataProcessor, bufferSize, inputValidator, configPath — the banned word is part of a larger descriptive name.\n\n3. MATH/SCIENCE VARIABLES: theta, radius, alpha, delta — domain-specific short names that carry clear meaning in context.\n\nEK9 bans exactly 12 non-descriptive names (E11031): temp, tmp, flag, flg, data, dat, object, obj, value, val, buffer, buf. These are case-insensitive. Names like str, num, item, result, input, output, count, index, list, map, set, key, status, state, type, config, param, arg, var, ret, res are NOT banned.\n\nSeparately, E11032 bans operator keywords as variable names: empty, length, contains, abs, sqrt, close, matches.\n\nThe rule is simple: bare generic names from the 12-name list are banned, but descriptive names (including compound words) are always fine.","ek9Example":"defines module qa.naming.allowedexceptions\n\n  defines function\n\n    <?-\n      Shows math variables — single-char and short science names.\n    -?>\n    computeHypotenuse() as pure\n      ->\n        x as Float\n        y as Float\n      <- hypotenuse as Float: sqrt(x * x + y * y)\n\n  defines program\n\n    AllowedExceptionsDemo()\n      stdout <- Stdout()\n\n      // ALLOWED: single-character math variables\n      x <- 3.0\n      y <- 4.0\n      z <- computeHypotenuse(x, y)\n      stdout.println(`Hypotenuse: ${z}`)\n\n      // ALLOWED: single-character loop counters\n      i <- 1\n      j <- 2\n      k <- 3\n      stdout.println(`Counters: ${i}, ${j}, ${k}`)\n\n      // ALLOWED: compound words with banned roots\n      connectionState <- \"active\"\n      dataProcessor <- \"xml\"\n      bufferSize <- 4096\n      configPath <- \"/etc/app.conf\"\n      stdout.println(`State: ${connectionState}`)\n      stdout.println(`Processor: ${dataProcessor}, Size: ${bufferSize}`)\n      stdout.println(`Config: ${configPath}`)\n\n      // ALLOWED: math/science domain names\n      theta <- 1.57\n      radius <- 10.0\n      stdout.println(`Theta: ${theta}, Radius: ${radius}`)","migrationContext":"","keywords":["E11031","allowed","compound","exception","math","naming","single-char"],"primaryTopics":["naming exceptions","allowed names"],"typicalErrors":[{"error":"E11031","correct":"      connectionState <- \"active\"\n      dataProcessor <- \"xml\"\n      bufferSize <- 4096\n      configPath <- \"/etc/app.conf\"\n      stdout.println(`State: ${connectionState}`)","incorrect":"      dat <- \"active\"\n      dataProcessor <- \"xml\"\n      bufferSize <- 4096\n      configPath <- \"/etc/app.conf\"\n      stdout.println(`State: ${dat}`)","explanation":"The bare name 'dat' is banned (E11031), but the compound word 'connectionState' is allowed because it describes what kind of state."}],"companions":[]}
{"id":924,"category":"Variable Naming Rules and Conventions","question":"How do I name function parameters without using banned names?","url":"https://ek9.io/qa/QA0924.html","alternatePhrasings":["What parameter names are allowed in EK9 functions?","How do I fix E11031 in function signatures?","What should I name my function arguments in EK9?"],"answer":"Function parameters follow the same banned name rules as all variables. Use names that describe what the parameter represents in the problem domain.\n\nEK9 bans exactly 12 non-descriptive names (E11031): temp, tmp, flag, flg, data, dat, object, obj, value, val, buffer, buf. These are case-insensitive. Names like input, output, param, arg, str, num, item, result, count, index, list, map, set, key, status, state, type, config, var, ret, res are NOT banned.\n\nSeparately, E11032 bans operator keywords as variable names: empty, length, contains, abs, sqrt, close, matches.\n\nINSTEAD OF BANNED PARAMS:\n- 'val' -> 'price', 'threshold', 'measurement'\n- 'tmp' -> 'swapHolder', 'intermediateResult'\n- 'dat' -> 'payload', 'sensorReading'\n- 'buf' -> 'readChunk', 'outputAccumulator'\n\nALWAYS ALLOWED EXCEPTIONS:\n- Single-character: x, y, z, i, j, k (math variables, loop counters)\n- Compound words: inputValidator, outputFormat, parameterCount\n- Descriptive: customerName, orderTotal, maxRetries","ek9Example":"defines module qa.naming.infunctions\n\n  defines function\n\n    <?-\n      Shows descriptive parameter naming in functions.\n    -?>\n    formatUserGreeting() as pure\n      ->\n        customerName as String\n        loyaltyTier as Integer\n      <-\n        greeting as String: String()\n\n      goldThreshold <- 5\n      tierLabel <- loyaltyTier > goldThreshold <- \"Gold\" : \"Silver\"\n      greeting :=? `${customerName} (${tierLabel})`\n\n    calculateShippingCost() as pure\n      ->\n        orderWeight as Float\n        destinationZone as Integer\n      <-\n        shippingCost as Float: orderWeight * #^ destinationZone * 0.5\n\n  defines program\n\n    InFunctionsDemo()\n      stdout <- Stdout()\n\n      // GOOD: descriptive parameter names\n      greeting <- formatUserGreeting(\"Alice\", 7)\n      stdout.println(`Greeting: ${greeting}`)\n\n      shippingCost <- calculateShippingCost(2.5, 3)\n      stdout.println(`Shipping: ${shippingCost}`)\n\n      // Single-char math vars: ALLOWED\n      x <- 5.0\n      y <- 10.0\n      stdout.println(`Dimensions: ${x} x ${y}`)\n\n      // Compound words: ALLOWED\n      inputValidator <- \"regex\"\n      parameterCount <- 4\n      stdout.println(`Validator: ${inputValidator}, Params: ${parameterCount}`)","migrationContext":"","keywords":["E11031","argument","banned","function","naming","parameter"],"primaryTopics":["function parameter naming","E11031"],"typicalErrors":[{"error":"E11031","correct":"        orderWeight as Float\n        destinationZone as Integer\n      <-\n        shippingCost as Float: orderWeight * #^ destinationZone * 0.5","incorrect":"        val as Float\n        destinationZone as Integer\n      <-\n        shippingCost as Float: val * #^ destinationZone * 0.5","explanation":"The parameter name 'val' is a banned non-descriptive name; use a meaningful identifier like 'orderWeight' that tells the caller what to pass. See ek9 -h E11031 for details."}],"companions":[]}
{"id":925,"category":"Variable Naming Rules and Conventions","question":"How do I name class fields without triggering E11031?","url":"https://ek9.io/qa/QA0925.html","alternatePhrasings":["What field names are banned in EK9 classes?","How do I fix E11031 in class properties?","What should I name my class attributes in EK9?"],"answer":"Class fields follow the same banned name rules as all variables. Use names that describe what the field represents in the domain model.\n\nEK9 bans exactly 12 non-descriptive names (E11031): temp, tmp, flag, flg, data, dat, object, obj, value, val, buffer, buf. These are case-insensitive. Names like status, state, config, handle, str, num, item, result, input, output, count, index, list, map, set, key, type, param, arg, var, ret, res are NOT banned.\n\nSeparately, E11032 bans operator keywords as variable names: empty, length, contains, abs, sqrt, close, matches.\n\nINSTEAD OF BANNED FIELDS:\n- 'val' -> 'orderTotal', 'measurementReading'\n- 'tmp' -> 'swapHolder', 'intermediateResult'\n- 'dat' -> 'sensorPayload', 'configEntry'\n- 'buf' -> 'readChunk', 'outputAccumulator'\n\nALWAYS ALLOWED EXCEPTIONS:\n- Single-character: x, y, z, i, j, k (math variables, loop counters)\n- Compound words: connectionState, errorCount, dataSource\n- Domain descriptive: customerName, orderTotal, retryLimit","ek9Example":"defines module qa.naming.inclasses\n\n  defines class\n\n    <?-\n      Shows descriptive field naming in a class.\n    -?>\n    SensorDevice\n      sensorId as String: String()\n      currentReading as Float: Float()\n      isCalibrated as Boolean: Boolean()\n\n      SensorDevice()\n        -> deviceId as String\n        sensorId :=: deviceId\n\n      recordMeasurement()\n        -> measuredValue as Float\n        currentReading := measuredValue\n        isCalibrated := true\n\n      operator $ as pure\n        <- rtn as String: `${sensorId}: ${currentReading}`\n\n      override operator ? as pure\n        <- rtn as Boolean: isCalibrated?\n\n  defines program\n\n    InClassesDemo()\n      stdout <- Stdout()\n\n      // GOOD: descriptive field names inside class\n      sensor <- SensorDevice(\"TH-001\")\n      sensor.recordMeasurement(22.5)\n      stdout.println($sensor)\n\n      // Compound words: ALLOWED as locals too\n      errorCount <- 0\n      dataSource <- \"network\"\n      stdout.println(`Errors: ${errorCount}, Source: ${dataSource}`)\n\n      // Single-char math: ALLOWED\n      x <- 1.0\n      y <- 2.0\n      stdout.println(`Position: ${x}, ${y}`)","migrationContext":"","keywords":["E11031","banned","class","field","naming","property"],"primaryTopics":["class field naming","E11031"],"typicalErrors":[{"error":"E11031","correct":"      dataSource <- \"network\"\n      stdout.println(`Errors: ${errorCount}, Source: ${dataSource}`)","incorrect":"      data <- \"network\"\n      stdout.println(`Errors: ${errorCount}, Source: ${data}`)","explanation":"The name 'data' is one of the 12 banned non-descriptive variable names, so declaring 'data' triggers E11031 — use a descriptive name like dataSource. See ek9 -h E11031 for details."}],"companions":[]}
{"id":926,"category":"Variable Naming Rules and Conventions","question":"Which of these names would be rejected by the EK9 compiler?","url":"https://ek9.io/qa/QA0926.html","alternatePhrasings":["Can you identify which variable names are banned in EK9?","Which identifiers will trigger E11031?","Help me spot banned names in my EK9 code"],"answer":"Here is how to spot banned names. EK9 bans exactly 12 non-descriptive names (E11031): temp, tmp, flag, flg, data, dat, object, obj, value, val, buffer, buf. These are case-insensitive.\n\nNames like str, num, item, result, input, output, count, index, list, map, set, key, status, state, type, config, param, arg, var, ret, res are NOT banned.\n\nSeparately, E11032 bans operator keywords as variable names: empty, length, contains, abs, sqrt, close, matches.\n\nSPOTTING GUIDE:\n- 'customerName' -> ALLOWED (descriptive)\n- 'name' -> ALLOWED (not in banned list)\n- 'data' -> BANNED (E11031)\n- 'dataProcessor' -> ALLOWED (compound word)\n- 'x' -> ALLOWED (single-char)\n- 'temp' -> BANNED (E11031)\n- 'temperature' -> ALLOWED (descriptive, not 'temp')\n- 'result' -> ALLOWED (not in the 12 banned names)\n- 'searchResult' -> ALLOWED (compound word)\n\nALWAYS ALLOWED EXCEPTIONS:\n- Single-character: x, y, z, i, j, k (math variables, loop counters)\n- Compound words: errorCount, inputValidator, configPath\n- Descriptive domain names: threshold, customerName, retryLimit","ek9Example":"defines module qa.naming.spotbanned\n\n  defines program\n\n    SpotBannedDemo()\n      stdout <- Stdout()\n\n      // ALLOWED: descriptive names\n      customerName <- \"Alice\"\n      temperature <- 22.5\n      searchResult <- \"found\"\n      retryLimit <- 3\n      stdout.println(`Customer: ${customerName}`)\n      stdout.println(`Temp: ${temperature}`)\n      stdout.println(`Search: ${searchResult}, Limit: ${retryLimit}`)\n\n      // ALLOWED: compound words (contain banned roots)\n      dataProcessor <- \"json\"\n      errorCount <- 0\n      inputValidator <- \"regex\"\n      configPath <- \"/etc/app\"\n      stdout.println(`Processor: ${dataProcessor}, Errors: ${errorCount}`)\n      stdout.println(`Validator: ${inputValidator}, Path: ${configPath}`)\n\n      // ALLOWED: single-character\n      x <- 10.0\n      y <- 20.0\n      i <- 1\n      stdout.println(`Point: ${x}, ${y}, Index: ${i}`)\n\n      // ALLOWED: domain names not in banned list\n      threshold <- 0.95\n      name <- \"sensor-A\"\n      stdout.println(`Threshold: ${threshold}, Name: ${name}`)","migrationContext":"","keywords":["E11031","allowed","banned","identify","naming","quiz","spot"],"primaryTopics":["identifying banned names","E11031"],"typicalErrors":[{"error":"E11031","correct":"      temperature <- 22.5\n      searchResult <- \"found\"\n      retryLimit <- 3\n      stdout.println(`Customer: ${customerName}`)\n      stdout.println(`Temp: ${temperature}`)\n      stdout.println(`Search: ${searchResult}, Limit: ${retryLimit}`)","incorrect":"      temp <- 22.5\n      searchResult <- \"found\"\n      retryLimit <- 3\n      stdout.println(`Customer: ${customerName}`)\n      stdout.println(`Temp: ${$temp}`)\n      stdout.println(`Search: ${searchResult}, Limit: ${$retryLimit}`)","explanation":"The name 'temp' is banned (E11031) but 'temperature' is allowed because it is a complete descriptive word, not the banned abbreviation."}],"companions":[]}
{"id":927,"category":"Variable Naming Rules and Conventions","question":"What naming mistakes do AI assistants commonly make in EK9?","url":"https://ek9.io/qa/QA0927.html","alternatePhrasings":["Why does AI-generated EK9 code fail with E11031?","What banned names do LLMs use most often?","How do I fix AI naming errors in EK9?"],"answer":"AI assistants (ChatGPT, Copilot, Claude) constantly generate banned variable names because they learned from Java/Python where these names are legal. The most common AI mistakes from the banned list are: temp, data, value, buffer, flag, object.\n\nEK9 bans exactly 12 non-descriptive names (E11031): temp, tmp, flag, flg, data, dat, object, obj, value, val, buffer, buf. These are case-insensitive. Names like result, count, item, input, output, status, str, num, index, list, map, set, key, state, type, config, param, arg, var, ret, res are NOT banned.\n\nSeparately, E11032 bans operator keywords as variable names: empty, length, contains, abs, sqrt, close, matches.\n\nAI FIX TABLE (for the 12 actually banned names):\n- AI writes 'temp'/'tmp' -> fix to 'swapHolder' or 'intermediateResult'\n- AI writes 'data'/'dat' -> fix to 'payload', 'sensorReading'\n- AI writes 'value'/'val' -> fix to 'price', 'threshold', 'measurement'\n- AI writes 'flag'/'flg' -> fix to 'isEnabled', 'hasPermission'\n- AI writes 'object'/'obj' -> fix to 'customer', 'sensorDevice'\n- AI writes 'buffer'/'buf' -> fix to 'readChunk', 'outputAccumulator'\n\nALWAYS ALLOWED EXCEPTIONS:\n- Single-character: x, y, z, i, j, k (math variables, loop counters)\n- Compound words: dataProcessor, resultSet, countdownTimer\n- Descriptive: customerName, orderTotal, retryLimit","ek9Example":"defines module qa.naming.aimistakes\n\n  defines function\n\n    <?-\n      Shows AI-corrected naming patterns.\n    -?>\n    fetchSensorData() as pure\n      <- payload as String: \"temperature=22.5\"\n\n    calculateTotal() as pure\n      ->\n        unitPrice as Float\n        orderQuantity as Integer\n      <- orderTotal as Float: unitPrice * #^ orderQuantity\n\n  defines program\n\n    AiMistakesDemo()\n      stdout <- Stdout()\n\n      // AI would write: data <- fetchSensorData()   BANNED\n      // FIXED: descriptive name\n      payload <- fetchSensorData()\n      stdout.println(`Payload: ${payload}`)\n\n      // AI would write: result <- calculateTotal(...)   BANNED\n      // FIXED: descriptive name\n      orderTotal <- calculateTotal(29.99, 3)\n      stdout.println(`Total: ${orderTotal}`)\n\n      // AI would write: count <- 5   BANNED\n      // FIXED: descriptive name\n      retryAttempt <- 5\n      stdout.println(`Retries: ${retryAttempt}`)\n\n      // Compound words: ALLOWED\n      dataProcessor <- \"xml\"\n      resultCache <- \"warm\"\n      stdout.println(`Processor: ${dataProcessor}, Cache: ${resultCache}`)\n\n      // Single-char math: ALLOWED\n      x <- 1.0\n      y <- 2.0\n      stdout.println(`Point: ${x}, ${y}`)","migrationContext":"","keywords":["AI","E11031","LLM","banned","common","fix","mistake","naming"],"primaryTopics":["AI naming mistakes","E11031 fixes"],"typicalErrors":[{"error":"E11031","correct":"      payload <- fetchSensorData()\n      stdout.println(`Payload: ${payload}`)","incorrect":"      data <- fetchSensorData()\n      stdout.println(`Payload: ${data}`)","explanation":"The non-descriptive name 'data' is banned by E11031; use a descriptive name such as 'payload' instead. See ek9 -h E11031 for details."}],"companions":[]}
{"id":928,"category":"Streams and Pipelines","question":"Why can't I use a void function in a stream pipeline in EK9?","url":"https://ek9.io/qa/QA0928.html","alternatePhrasings":["What triggers E10020 in EK9 stream pipelines?","Why does map reject my function that returns nothing?","Can I use a procedure in a stream pipeline?"],"answer":"Void functions can't be used in stream pipelines. Streams pass values from stage to stage, and a void function produces nothing to pass along.\n\nSTREAM DATA FLOW\nEvery pipeline stage transforms or filters elements. The output of one stage feeds the next:\n  cat items | map with transform | filter by predicate | collect as List of T\nIf 'transform' returns void, there is nothing for 'filter' to receive.\n\nCORRECT: Function returns a value\n  toUpper() as pure\n    -> item as String\n    <- rtn as String: item.upperCase()\n  cat words | map with toUpper | collect as List of String\n\nINCORRECT: Void function in pipeline\n  logItem()\n    -> item as String\n    stdout.println(item)        // returns nothing\n  cat words | map with logItem  // ERROR: void produces no output\n\nFor side effects, use 'tee' with a collection, or redirect with '> stdout'.\n\nSee Q235 for stream operations. See Q866 for void with call. See Q843 for return requirements.","ek9Example":"defines module qa.streams.void.not.allowed\n\n  defines function\n\n    toUpper() as pure\n      -> item as String\n      <- rtn as String: item.upperCase()\n\n  defines program\n\n    StreamVoidDemo()\n      stdout <- Stdout()\n\n      //Demonstrate that toUpper returns a value — required for stream pipelines\n      uppered <- toUpper(\"hello\")\n      stdout.println(`Uppercased: ${uppered}`)\n\n      //A function that returns a value can be used in map\n      stdout.println(toUpper(\"world\"))","migrationContext":"Java: Stream.map() requires Function<T,R> — Consumer (void) not allowed. Python: map() expects a return value. Rust: iter().map() requires FnMut(T) -> U. Go: no streams. EK9: pipeline stages require value-producing functions.","keywords":["E10020","function","map","pipeline","produce","stream","value","void"],"primaryTopics":["void in streams","E10020","pipeline data flow"],"typicalErrors":[],"companions":[]}
{"id":929,"category":"Streams and Pipelines","question":"How do filter and sort work together in EK9 streams?","url":"https://ek9.io/qa/QA0929.html","alternatePhrasings":["Can I chain filter and sort in an EK9 stream?","How do I filter then sort a list in EK9?","What is the correct order for filter and sort in EK9 pipelines?"],"answer":"Chain filter and sort with the pipe operator. Filter first to reduce the dataset, then sort the remaining elements.\n\nPATTERN\n  cat items | filter by predicate | sort | collect as List of T\n  cat items | filter by predicate | sort > stdout\n\nFILTER narrows the stream to elements matching the predicate (a function returning Boolean). SORT orders the remaining elements using their natural ordering (the <=> operator).\n\nWITH STRINGS\n  cat names | filter by startsWithA | sort > stdout\nFilters to names starting with 'A', then alphabetically sorts them.\n\nWITH FLOATS\n  cat temperatures | filter by aboveFreezing | sort | collect as List of Float\nKeeps temperatures above zero, sorts ascending.\n\nFiltering before sorting is more efficient: sort operates on fewer elements.\n\nSee Q235 for all stream operations. See Q896 for output redirection. See Q237 for streams vs loops.","ek9Example":"defines module qa.streams.filter.sort.example\n\n  defines function\n\n    aboveFreezing() as pure\n      -> temperature as Float\n      <- rtn as Boolean: temperature > 0.0\n\n    isLongName() as pure\n      -> nameToCheck as String\n      <- rtn as Boolean?\n      minLength <- 4\n      rtn: length nameToCheck > minLength\n\n  defines program\n\n    FilterSortDemo()\n      stdout <- Stdout()\n\n      //Demonstrate filter predicates that streams would use\n      stdout.println(`15.5 above freezing: ${aboveFreezing(15.5)}`)\n      stdout.println(`-3.2 above freezing: ${aboveFreezing(-3.2)}`)\n\n      //Demonstrate string predicate for stream filter\n      stdout.println(`James is long: ${isLongName(\"James\")}`)\n      stdout.println(`Bob is long: ${isLongName(\"Bob\")}`)","migrationContext":"Java: stream().filter(pred).sorted().collect(). Python: sorted(filter(pred, items)). Rust: iter().filter(pred).sorted(). EK9: cat items | filter by pred | sort | collect.","keywords":["chain","filter","order","pipeline","predicate","sort","stream"],"primaryTopics":["filter and sort","stream chaining","pipeline ordering"],"typicalErrors":[],"companions":[]}
{"id":930,"category":"Streams and Pipelines","question":"How do I transform stream elements and take the first N in EK9?","url":"https://ek9.io/qa/QA0930.html","alternatePhrasings":["How does map with head work in EK9 streams?","Can I transform then limit results in an EK9 pipeline?","How do I take the first few transformed items from a stream?"],"answer":"Use 'map with' to transform each element, then 'head N' to take only the first N results.\n\nBASIC PATTERN\n  cat source | map with transformer | head N | collect as List of R\n\nThe map stage applies a function to every element, changing its type or value. The head stage stops after N elements pass through, discarding the rest.\n\nEXAMPLE: Transform integers to strings, take first 3\n  cat [10, 20, 30, 40, 50] | map with intToLabel | head 3 | collect as List of String\n  Result: [\"Item-10\", \"Item-20\", \"Item-30\"]\n\nEXAMPLE: Double values, take top 2\n  cat [5, 1, 8, 3] | map with doubler | head 2 > stdout\n  Output: 10 then 2\n\nHead is efficient because it stops consuming from upstream once N elements have passed.\n\nSee Q235 for all operations. See Q125 for head/tail/skip details.","ek9Example":"defines module qa.streams.map.head.example\n\n  defines function\n\n    doubler() as pure\n      -> number as Integer\n      <- rtn as Integer: number * 2\n\n    intToLabel() as pure\n      -> number as Integer\n      <- rtn as String: `Item-${number}`\n\n  defines program\n\n    MapHeadDemo()\n      stdout <- Stdout()\n\n      //Demonstrate transform functions that streams would use with map\n      stdout.println(`doubler(10): ${doubler(10)}`)\n      stdout.println(`doubler(20): ${doubler(20)}`)\n\n      //Demonstrate intToLabel transform\n      stdout.println(intToLabel(30))\n      stdout.println(intToLabel(40))","migrationContext":"Java: stream().map(fn).limit(n).collect(). Python: list(map(fn, items))[:n]. Rust: iter().map(fn).take(n).collect(). EK9: cat items | map with fn | head n | collect.","keywords":["first","head","limit","map","pipeline","stream","take","transform"],"primaryTopics":["map and head","transform and limit","stream pipeline"],"typicalErrors":[],"companions":[]}
{"id":931,"category":"Streams and Pipelines","question":"What do tail and skip do in EK9 stream pipelines?","url":"https://ek9.io/qa/QA0931.html","alternatePhrasings":["How do I get the last N elements from a stream in EK9?","How do I skip elements at the start of an EK9 stream?","Can I combine tail and skip in EK9?"],"answer":"Tail keeps the last N elements. Skip discards the first N elements. Both are positional operations in the pipeline.\n\nTAIL N\nKeeps only the final N elements that flow through the stream:\n  cat [1, 2, 3, 4, 5] | tail 2 | collect as List of Integer\n  Result: [4, 5]\n\nSKIP N\nDiscards the first N elements, passing the rest downstream:\n  cat [1, 2, 3, 4, 5] | skip 2 | collect as List of Integer\n  Result: [3, 4, 5]\n\nCOMBINED\nSkip the first 2, then take only the last 2 of what remains:\n  cat [1, 2, 3, 4, 5, 6] | skip 2 | tail 2 | collect as List of Integer\n  Result: [5, 6]  (skip gives [3,4,5,6], tail 2 gives [5,6])\n\nThink of skip as Unix 'tail -n +N' and tail as Unix 'tail -N'.\n\nSee Q235 for all stream operations. See Q125 for head/tail/skip. See Q896 for redirect. See Q954 for collect as custom type.","ek9Example":"defines module qa.streams.tail.skip.example\n\n  defines program\n\n    TailSkipDemo()\n      stdout <- Stdout()\n\n      values <- [10, 20, 30, 40, 50, 60, 70]\n\n      //Demonstrate list operations that mirror tail and skip concepts\n      stdout.println(`Full list: ${values}`)\n      stdout.println(`List length: ${length values}`)\n\n      //Access elements by index using getOrDefault\n      defaultVal <- 0\n      firstElement <- values.getOrDefault(0, defaultVal)\n      if firstElement?\n        stdout.println(`First element: ${firstElement}`)\n\n      lastIndex <- length values - 1\n      lastElement <- values.getOrDefault(lastIndex, defaultVal)\n      if lastElement?\n        stdout.println(`Last element: ${lastElement}`)","migrationContext":"Java: stream().skip(n) and no direct tail — need collect then subList. Python: islice(iter, n, None) for skip, deque(iter, maxlen=n) for tail. Rust: iter().skip(n) and no direct last-n. Go: manual slicing. EK9: skip N and tail N as built-in pipeline stages.","keywords":["discard","last","pipeline","positional","skip","stream","tail"],"primaryTopics":["tail operation","skip operation","positional stream stages"],"typicalErrors":[],"companions":[]}
{"id":932,"category":"Control Flow","question":"Can I catch multiple exception types in one catch block in EK9?","url":"https://ek9.io/qa/QA0932.html","alternatePhrasings":["Does EK9 support multi-catch like Java?","What triggers E07850 in EK9 catch blocks?","How many exception types can a catch handle in EK9?"],"answer":"Each EK9 catch block handles exactly ONE exception type. There is no multi-catch syntax.\n\nSINGLE CATCH\n  try\n    riskyWork()\n  catch\n    -> ex as Exception\n    stderr.println(`Failed: ${ex}`)\n\nThe catch uses -> on its own line to declare the incoming exception. This follows EK9's data-flow convention: -> means data flowing IN.\n\nWHY ONE TYPE PER CATCH\nMulti-catch (Java's catch (A | B e)) creates ambiguity about the exception's actual type inside the handler. EK9 enforces clarity: you know the exact type you are handling.\n\nHANDLING DIFFERENT TYPES\nUse separate try/catch blocks or catch the base Exception type:\n  try\n    riskyWork()\n  catch\n    -> ex as Exception\n    stderr.println(`Caught: ${ex}`)\n\nSince all EK9 exceptions descend from Exception, catching Exception handles everything.\n\nSee Q902 for try/catch basics. See Q934 for try/finally. See Q130 for control flow overview.","ek9Example":"defines module qa.controlflow.singlecatch\n\n  defines function\n\n    safeDivision() as pure\n      ->\n        numerator as Integer\n        denominator as Integer\n      <-\n        rtn as Float: #^ numerator / #^ denominator\n\n  defines program\n\n    SingleCatchDemo()\n      stdout <- Stdout()\n      stderr <- Stderr()\n\n      //Single exception type per catch\n      try\n        outcome <- safeDivision(42, 7)\n        stdout.println(`42 / 7 = ${outcome}`)\n      catch\n        -> ex as Exception\n        stderr.println(`Division failed: ${ex}`)\n\n      //Catching the base Exception covers all exception types\n      try\n        second <- safeDivision(100, 5)\n        stdout.println(`100 / 5 = ${second}`)\n      catch\n        -> ex as Exception\n        stderr.println(`Error: ${ex}`)\n      finally\n        stdout.println(\"Division attempt complete\")","migrationContext":"Java: catch (IOException | SQLException e) multi-catch since Java 7. Python: except (TypeError, ValueError) as e. C#: catch (Exception e) when — filter pattern. EK9: one type per catch block, no multi-catch.","keywords":["E07850","catch","exception","handler","multi-catch","single","try","type"],"primaryTopics":["single catch type","E07850","exception handling"],"typicalErrors":[{"error":"E07850","correct":"      catch\n        -> ex as Exception\n        stderr.println(`Division failed: ${ex}`)","incorrect":"      catch\n        ->\n          ex1 as Exception\n          ex2 as Exception\n        stderr.println(`Failed: ${ex1}`)","explanation":"An EK9 catch block's arrow may declare only ONE exception variable (E07850). Declaring two (ex1, ex2) in a single catch is rejected. Use one catch block, catching Exception to handle all types. See ek9 -h E07850 for details."}],"companions":[]}
{"id":933,"category":"Control Flow","question":"What happens if I write code after a throw statement in EK9?","url":"https://ek9.io/qa/QA0933.html","alternatePhrasings":["What triggers E07370 or E07380 in EK9?","Does EK9 detect unreachable code?","Can I have statements after throw in EK9?"],"answer":"EK9 detects unreachable code and flags it as a compile error. Code placed after an unconditional throw can never execute.\n\nUNREACHABLE CODE DETECTION\nThe compiler performs flow analysis. When every path through a block ends with throw, any subsequent code is dead:\n  try\n    processData()\n  catch\n    -> ex as Exception\n    stderr.println(`Error: ${ex}`)\n\nEK9 treats unreachable statements as errors, not warnings. Dead code indicates a logic flaw that must be fixed.\n\nPROPER PATTERN\nPlace all meaningful work before the throw. If conditional logic determines whether to throw, the compiler tracks each branch independently.\n\nCONDITIONAL THROW IS FINE\n  if not isValid\n    throw Exception(\"invalid\")\n  //Code here IS reachable because the throw is conditional\n  processValid()\n\nSee Q902 for try/catch basics. See Q932 for single catch type. See Q144 for control flow philosophy.","ek9Example":"defines module qa.controlflow.unreachable\n\n  defines function\n\n    validateAge() as pure\n      -> age as Integer\n      <- rtn as Boolean: age >= 0\n\n  defines program\n\n    UnreachableDemo()\n      stdout <- Stdout()\n      stderr <- Stderr()\n\n      age <- 25\n\n      //Conditional throw — code after is reachable\n      if not validateAge(age)\n        throw Exception(\"Invalid age\")\n\n      //This line is reachable because throw is conditional\n      stdout.println(`Valid age: ${age}`)\n\n      //Proper try/catch with no dead code\n      try\n        if not validateAge(-1)\n          throw Exception(\"Negative age\")\n        stdout.println(\"Age validated\")\n      catch\n        -> ex as Exception\n        stderr.println(`Caught: ${ex}`)","migrationContext":"Java: unreachable statement is a compile error. C#: unreachable code warning (CS0162). Python: no unreachable detection. Rust: warns on unreachable code. Go: no unreachable detection. EK9: compile error for unreachable code after throw.","keywords":["E07370","E07380","analysis","code","dead","flow","throw","unreachable"],"primaryTopics":["unreachable code","throw flow","dead code detection"],"typicalErrors":[{"error":"E07370","correct":"        if not validateAge(-1)\n          throw Exception(\"Negative age\")\n        stdout.println(\"Age validated\")","incorrect":"        throw Exception(\"Negative age\")\n        stdout.println(\"Age validated\")","explanation":"Code placed after an unconditional throw can never execute and is rejected as unreachable. See ek9 -h E07370 for details."}],"companions":[]}
{"id":934,"category":"Control Flow","question":"When should I use try/finally without catch in EK9?","url":"https://ek9.io/qa/QA0934.html","alternatePhrasings":["Does EK9 support try/finally without catch?","How do I ensure cleanup code runs in EK9?","What is the finally block for in EK9?"],"answer":"The finally block runs regardless of whether an exception occurs. Use try/finally without catch when you want cleanup but not error handling at that level.\n\nTRY/FINALLY PATTERN\n  try\n    doWork()\n  finally\n    cleanup()       // Always runs\n\nThe exception propagates up to the caller. The finally block still executes before propagation.\n\nTRY/CATCH/FINALLY\n  try\n    doWork()\n  catch\n    -> ex as Exception\n    logError(ex)\n  finally\n    cleanup()       // Runs after catch too\n\nFinally runs in all scenarios: normal completion, exception caught, or exception propagating.\n\nWHEN TO OMIT CATCH\nOmit catch when the current scope cannot meaningfully handle the error. Let exceptions propagate to a higher-level handler while still guaranteeing local cleanup.\n\nSee Q902 for try/catch syntax. See Q932 for single catch type. See Q935 for nested try/catch.","ek9Example":"defines module qa.controlflow.tryfinally\n\n  defines function\n\n    riskyCalculation() as pure\n      ->\n        x as Integer\n        y as Integer\n      <-\n        rtn as Float: #^ x / #^ y\n\n  defines program\n\n    TryFinallyDemo()\n      stdout <- Stdout()\n\n      //try/finally without catch — cleanup guaranteed\n      stdout.println(\"Starting work\")\n      try\n        result <- riskyCalculation(20, 4)\n        stdout.println(`Result: ${result}`)\n      finally\n        stdout.println(\"Cleanup done\")\n\n      //try/catch/finally — full pattern\n      try\n        second <- riskyCalculation(50, 10)\n        stdout.println(`Second: ${second}`)\n      catch\n        -> ex as Exception\n        stdout.println(`Handled: ${ex}`)\n      finally\n        stdout.println(\"All finished\")","migrationContext":"Java: try-finally for cleanup, try-with-resources preferred. Python: try/finally, context managers preferred. Rust: Drop trait, no try/finally. Go: defer for cleanup. EK9: try/finally with optional catch.","keywords":["cleanup","exception","finally","guarantee","propagate","resource","try"],"primaryTopics":["try finally","cleanup pattern","exception propagation"],"typicalErrors":[],"companions":[]}
{"id":935,"category":"Control Flow","question":"Can I nest try/catch blocks in EK9?","url":"https://ek9.io/qa/QA0935.html","alternatePhrasings":["How do nested exception handlers work in EK9?","Can I put a try inside another try in EK9?","How do I handle different errors at different levels in EK9?"],"answer":"Nested try/catch blocks work in EK9. An inner try/catch handles errors at a fine-grained level while the outer handler catches anything that escapes.\n\nNESTED PATTERN\n  try\n    //Outer work\n    try\n      //Inner work that might fail\n      riskyStep()\n    catch\n      -> innerEx as Exception\n      handleInner(innerEx)\n    //Continue outer work after inner handled\n    moreWork()\n  catch\n    -> outerEx as Exception\n    handleOuter(outerEx)\n\nIf the inner catch handles the exception, execution continues in the outer try. If the inner catch re-throws or if an exception occurs outside the inner try, the outer catch handles it.\n\nWHEN TO NEST\nNest when different operations need different error handling strategies. The inner handler deals with a specific failure while the outer provides a safety net.\n\nSee Q902 for try/catch basics. See Q932 for single catch type. See Q934 for try/finally.","ek9Example":"defines module qa.controlflow.nestedtry\n\n  defines function\n\n    parseInteger() as pure\n      -> text as String\n      <- rtn as Integer: Integer(text)\n\n  defines program\n\n    NestedTryCatchDemo()\n      stdout <- Stdout()\n      stderr <- Stderr()\n\n      //First try/catch handles one operation\n      try\n        parsed <- parseInteger(\"42\")\n        stdout.println(`Parsed: ${parsed}`)\n      catch\n        -> firstEx as Exception\n        stderr.println(`First parse failed: ${firstEx}`)\n\n      //Second try/catch handles another operation\n      try\n        second <- parseInteger(\"99\")\n        stdout.println(`Second parse: ${second}`)\n      catch\n        -> secondEx as Exception\n        stderr.println(`Second parse failed: ${secondEx}`)\n      finally\n        stdout.println(\"All operations complete\")","migrationContext":"Java: nested try/catch common for layered error handling. Python: nested try/except same pattern. Rust: nested match on Result values. Go: nested if err != nil checks. EK9: nested try/catch with -> syntax for exception parameter.","keywords":["catch","exception","handler","inner","layered","nested","outer","try"],"primaryTopics":["nested try catch","layered exception handling"],"typicalErrors":[],"companions":[]}
{"id":936,"category":"Operators and Expressions","question":"Is the ? operator prefix or suffix in EK9?","url":"https://ek9.io/qa/QA0936.html","alternatePhrasings":["Where does the ? go — before or after the variable name?","How do I write an isSet check correctly in EK9?","Is it ?name or name? in EK9?"],"answer":"The ? operator is always SUFFIX in EK9. Write name? not ?name.\n\nSUFFIX SYNTAX\n  if userName?\n    stdout.println(userName)\nThe ? comes after the variable, like Rust's .is_some() or Swift's optional unwrapping.\n\nWHY SUFFIX\nSuffix reads naturally as a question: 'is userName set?' The ? asks a question about the thing to its left. Prefix ? would be ambiguous with other operators.\n\nUSAGE CONTEXTS\n  variable?           // Is this variable set?\n  list?               // Is this list set? (always true for created lists)\n  result?             // Is this result set?\n  if connection?      // Guard: only enter if connection is set\n\nCOMBINING WITH NOT\n  if not userName?\n    stdout.println(\"No user name\")\n\nThe pattern 'not variable?' reads as 'not (variable is set)'.\n\nSee Q877 for isSet details. See Q898 for more ? examples. See Q29 for tri-state.","ek9Example":"defines module qa.operators.issetsuffix\n\n  defines program\n\n    IsSetSuffixDemo()\n      stdout <- Stdout()\n\n      //Unset variable — ? returns false\n      greeting <- String()\n      if not greeting?\n        stdout.println(\"Variable is unset\")\n\n      //Guarded assignment with suffix check\n      greeting :=? \"Hello\"\n      if greeting?\n        stdout.println(greeting)\n\n      //Works on any type\n      score <- Integer()\n      if not score?\n        stdout.println(\"Score is not yet set\")\n\n      score :=? 42\n      if score?\n        stdout.println(`Score is set: ${score}`)","migrationContext":"EK9 uses value? as a suffix — append ? after the variable name to check if it is set. Returns Boolean, no parentheses needed.","keywords":["check","isset","mark","operator","position","question","set","suffix"],"primaryTopics":["suffix ? operator","isSet position","operator placement"],"typicalErrors":[],"companions":[]}
{"id":937,"category":"Operators and Expressions","question":"Why does EK9 reject non-Boolean values in if conditions?","url":"https://ek9.io/qa/QA0937.html","alternatePhrasings":["What triggers E07540 in EK9?","Can I use an Integer as a condition in EK9?","Why won't if count work when count is an Integer?"],"answer":"EK9 requires Boolean expressions in Boolean contexts. Unlike C or JavaScript, there is no implicit truthiness conversion.\n\nBOOLEAN CONTEXT REQUIRES BOOLEAN\nConditions in if, while, and other control flow must evaluate to Boolean:\n  if count > 0          // CORRECT: comparison produces Boolean\n  if name?              // CORRECT: ? produces Boolean\n  while isActive        // CORRECT: isActive is Boolean\n\nNO IMPLICIT CONVERSION\nThese fail because the value is not Boolean:\n  if count              // ERROR: Integer is not Boolean\n  if name               // ERROR: String is not Boolean\n  while connectionObj   // ERROR: object is not Boolean\n\nUSE ? FOR SET CHECKS\nTo check if a non-Boolean value is set, use the ? suffix operator:\n  if count?             // Is count set? Returns Boolean\n  if name?              // Is name set? Returns Boolean\n\nUSE COMPARISONS FOR VALUE CHECKS\n  if count > 0          // Numeric comparison returns Boolean\n  if length name > 0    // String has content\n\nThis eliminates an entire class of bugs from truthy/falsy confusion in JavaScript and Python.\n\nSee Q877 for isSet operator. See Q936 for ? suffix position. See Q30 for Boolean operators.","ek9Example":"defines module qa.operators.booleancontext\n\n  defines program\n\n    BooleanContextDemo()\n      stdout <- Stdout()\n\n      //? operator produces Boolean — correct\n      userName <- String()\n      userName :=? \"Alice\"\n      if userName?\n        stdout.println(`Name is set: ${userName}`)\n\n      //Comparison produces Boolean — correct\n      score <- Integer()\n      score :=? 5\n      if score?\n        if score > 0\n          stdout.println(`Score is positive: ${score}`)\n\n      //Compound Boolean expressions — correct\n      threshold <- 10\n      if score?\n        if score > 0 and score < threshold\n          stdout.println(\"Score in range\")","migrationContext":"JavaScript: if (count) truthy for non-zero. Python: if count truthy for non-zero/non-empty. C: if (ptr) truthy for non-null. Java: Boolean required, no implicit conversion. Rust: Boolean required, like EK9. EK9: Boolean required, use ? for isSet check or comparisons.","keywords":["E07540","boolean","condition","context","conversion","implicit","truthiness","type"],"primaryTopics":["Boolean context","E07540","no implicit truthiness"],"typicalErrors":[{"error":"E07540","correct":"        if score > 0\n          stdout.println(`Score is positive: ${score}`)","incorrect":"        if score\n          stdout.println(`Score is positive: ${score}`)","explanation":"A non-Boolean value like the Integer 'score' cannot be used as an 'if' condition — use a comparison such as 'score > 0'. See ek9 -h E07540 for details."}],"companions":[]}
{"id":938,"category":"Control Flow","question":"How do I declare the return variable inside a switch expression in EK9?","url":"https://ek9.io/qa/QA0938.html","alternatePhrasings":["What is the <- rtn pattern in switch expressions?","How does switch return a value in EK9?","Why do I need a return declaration inside switch?"],"answer":"A switch expression requires a return variable declaration immediately after the switch line. Each case assigns to that variable.\n\nPATTERN\n  result := switch value\n    <- label as String: String()\n    case \"A\"\n      label: \"Alpha\"\n    case \"B\"\n      label: \"Beta\"\n    default\n      label: \"Other\"\n\nThe '<- label as String: String()' declares a variable named 'label' initialized to an empty String. Each case assigns to label. After the switch, result holds label's final value.\n\nDECLARATION FORM\n  result <- switch expr\n    <- rtn as Type?                  // Unset, nullable style\n  result := switch expr\n    <- rtn as Type: initialValue     // With default\n  result := switch expr\n    <- rtn <- Type()                 // Constructor initialization\n\nTHE VARIABLE NAME IS YOURS TO CHOOSE\nUse any meaningful name: rtn, category, label, outcome. The name should describe what the switch computes.\n\nNO RETURN STATEMENT\nEK9 has no return keyword. The declared variable's final value IS the result. The compiler verifies every path assigns it.\n\nSee Q68 for switch expression basics. See Q901 for expression vs statement. See Q782 for return rules.","ek9Example":"defines module qa.controlflow.switchreturn\n\n  defines function\n\n    classifyHttpCode() as pure\n      -> code as Integer\n      <-\n        rtn as String: String()\n\n      rtn := switch code\n        <- category as String: String()\n        case 200\n          category: \"OK\"\n        case 301\n          category: \"Redirect\"\n        case 404\n          category: \"Not Found\"\n        case 500\n          category: \"Server Error\"\n        default\n          category: \"Unknown\"\n\n    describePriority() as pure\n      -> level as Integer\n      <-\n        rtn as String: String()\n\n      rtn := switch level\n        <- desc as String: String()\n        case 1\n          desc: \"Critical\"\n        case 2\n          desc: \"High\"\n        case 3\n          desc: \"Medium\"\n        default\n          desc: \"Low\"\n\n  defines program\n\n    SwitchReturnDemo()\n      stdout <- Stdout()\n\n      httpResult <- classifyHttpCode(404)\n      stdout.println(`HTTP 404: ${httpResult}`)\n\n      okResult <- classifyHttpCode(200)\n      stdout.println(`HTTP 200: ${okResult}`)\n\n      priority <- describePriority(1)\n      stdout.println(`Priority 1: ${priority}`)\n\n      lowPriority <- describePriority(9)\n      stdout.println(`Priority 9: ${lowPriority}`)","migrationContext":"Java: switch expression with yield keyword. Kotlin: when returns last expression in branch. Rust: match returns last expression. C#: switch expression with =>. EK9: explicit return variable declaration with <- inside the switch block.","keywords":["assign","case","declaration","expression","result","return","switch","variable"],"primaryTopics":["switch return declaration","switch expression pattern"],"typicalErrors":[{"error":"E07405","correct":"      rtn := switch code\n        <- category as String: String()","incorrect":"      rtn := switch code","explanation":"Switch expressions require a return variable declaration (<- rtn as Type). Without it, the switch cannot produce a value. See ek9 -h E07405 for details."}],"companions":[]}
{"id":939,"category":"Operators and Expressions","question":"What do <? and >? mean in EK9 — are they null coalescing?","url":"https://ek9.io/qa/QA0939.html","alternatePhrasings":["Is <? the null coalescing operator in EK9?","How do min/max coalescing operators work in EK9?","What is the difference between <? and ?? in EK9?"],"answer":"No. <? and >? are MINIMUM and MAXIMUM coalescing operators, not null coalescing.\n\n<? RETURNS THE SMALLER VALUE\n  minimum <- 10 <? 3       // Result: 3\n  minimum <- 7 <? 20       // Result: 7\n\n>? RETURNS THE LARGER VALUE\n  maximum <- 10 >? 3       // Result: 10\n  maximum <- 7 >? 20       // Result: 20\n\nUNSET HANDLING\nWhen one operand is unset, the other is returned:\n  a <- 5\n  b <- Integer()\n  result <- a <? b         // Result: 5 (b is unset, a wins)\n\nNOT NULL COALESCING\nFor null/unset coalescing, EK9 has separate operators:\n  ?? checks memory presence (absent check)\n  ?: checks isSet state (elvis operator)\n  :=? assigns only if target is unset\n\nREMEMBER\n  <? = minimum (pick smaller)\n  >? = maximum (pick larger)\n  ?? = present check (memory)\n  ?: = set check (isSet)\n\nSee Q243 for all coalescing operators. See Q239 for comparison operators.","ek9Example":"defines module qa.operators.coalescingminmax\n\n  defines function\n\n    minOf() as pure\n      ->\n        left as Integer\n        right as Integer\n      <- rtn as Integer: left <? right\n\n    maxOf() as pure\n      ->\n        left as Integer\n        right as Integer\n      <- rtn as Integer: left >? right\n\n    minOfFloat() as pure\n      ->\n        left as Float\n        right as Float\n      <- rtn as Float: left <? right\n\n    maxOfFloat() as pure\n      ->\n        left as Float\n        right as Float\n      <- rtn as Float: left >? right\n\n    chainedMin() as pure\n      ->\n        a as Integer\n        b as Integer\n        c as Integer\n      <- rtn as Integer: a <? b <? c\n\n    chainedMax() as pure\n      ->\n        a as Integer\n        b as Integer\n        c as Integer\n      <- rtn as Integer: a >? b >? c\n\n  defines program\n\n    CoalescingMinMaxDemo()\n      stdout <- Stdout()\n\n      //<? gives the smaller value\n      minResult <- minOf(15, 8)\n      stdout.println(`min(15, 8) = ${minResult}`)\n\n      //>? gives the larger value\n      maxResult <- maxOf(15, 8)\n      stdout.println(`max(15, 8) = ${maxResult}`)\n\n      //Works with Float too\n      smallFloat <- minOfFloat(3.14, 2.72)\n      stdout.println(`min(3.14, 2.72) = ${smallFloat}`)\n\n      bigFloat <- maxOfFloat(3.14, 2.72)\n      stdout.println(`max(3.14, 2.72) = ${bigFloat}`)\n\n      //Chained: find minimum of three values\n      smallest <- chainedMin(20, 5, 12)\n      stdout.println(`min(20, 5, 12) = ${smallest}`)\n\n      largest <- chainedMax(20, 5, 12)\n      stdout.println(`max(20, 5, 12) = ${largest}`)","migrationContext":"Perl: min/max functions. Ruby: [a,b].min and [a,b].max. Python: min(a,b) and max(a,b). Rust: std::cmp::min/max. Java: Math.min/max. EK9: <? and >? as infix operators with unset-safe coalescing.","keywords":["coalescing","compare","greater","lesser","maximum","minimum","operator","unset"],"primaryTopics":["min/max coalescing","<? and >? operators"],"typicalErrors":[],"companions":[]}
{"id":940,"category":"Operators and Expressions","question":"Can I use ++ in an assignment like y <- x++ in EK9?","url":"https://ek9.io/qa/QA0940.html","alternatePhrasings":["Are ++ and -- expressions or statements in EK9?","Why can't I assign the result of ++ in EK9?","How do increment and decrement work in EK9?"],"answer":"EK9 has ++ and -- but they are STATEMENT-ONLY. They modify the variable in place and produce no value. You cannot use them inside expressions.\n\nSTATEMENT-ONLY MEANS\n  counter++          CORRECT — standalone statement\n  counter--          CORRECT — standalone statement\n  y <- counter++     WRONG — ++ produces no value to assign\n  if counter++       WRONG — ++ produces no Boolean\n\nWHY THIS DESIGN\nIn C and Java, x++ returns the OLD value while ++x returns the NEW value. This causes subtle bugs:\n  array[i++] = array[++j]    // Which updates first?\nEK9 eliminates this entire bug category by making ++ and -- void operations.\n\nIF YOU NEED THE OLD VALUE\n  saved <- counter\n  counter++\n  // saved has old value, counter has new value\n\nThis is the same design choice as Go, which also makes ++ and -- statements only.\n\nSee Q780 for detailed explanation. See Q238 for operator overview.","ek9Example":"defines module qa.operators.incrementstmt\n\n  defines program\n\n    IncrementDemo()\n      stdout <- Stdout()\n\n      counter <- 0\n\n      //Correct: ++ as standalone statement\n      counter++\n      counter++\n      counter++\n      stdout.println(`After 3 increments: ${counter}`)\n\n      //Correct: -- as standalone statement\n      counter--\n      stdout.println(`After decrement: ${counter}`)\n\n      //Save before incrementing if you need the old value\n      previous <- counter\n      counter++\n      stdout.println(`Was ${previous}, now ${counter}`)","migrationContext":"C/C++: pre/post increment as expressions, undefined behavior in complex expressions. Java: pre/post increment as expressions. Go: ++ and -- are statements only (same as EK9). Python: no ++ or -- at all. Rust: no ++ or -- at all. EK9: ++ and -- are statements only, like Go.","keywords":["E07950","assign","bug","decrement","expression","increment","statement","void"],"primaryTopics":["statement-only increment","++ and -- restrictions"],"typicalErrors":[{"error":"E07950","correct":"      counter++\n      counter++","incorrect":"      snapshot <- counter++","explanation":"The ++ operator is statement-only in EK9 — it returns void and cannot be used in expressions. Increment on its own line, then read the value. See ek9 -h E07950 for details."}],"companions":[]}
{"id":941,"category":"Operators and Expressions","question":"Why can't I assign the result of a void function in EK9?","url":"https://ek9.io/qa/QA0941.html","alternatePhrasings":["What triggers E10010 in EK9?","What happens when I try to store void in a variable?","Can a void function return a value in EK9?"],"answer":"Void functions produce no value. Attempting to assign their result to a variable triggers E10010.\n\nVOID FUNCTIONS HAVE NO RETURN\nA function without a <- return declaration is void:\n  logMessage()\n    -> msg as String\n    stdout.println(msg)       // No <- so this is void\n\nYou CANNOT assign void:\n  result <- logMessage(\"hi\")  // ERROR E10010 — nothing to assign\n\nFUNCTIONS THAT RETURN VALUES\nA function with <- produces a value you can capture:\n  formatMessage() as pure\n    -> msg as String\n    <- rtn as String: `[LOG] ${msg}`\n\n  result <- formatMessage(\"hi\")   // CORRECT — function returns String\n\nVOID IS FOR SIDE EFFECTS\nVoid functions perform actions: printing, writing files, sending messages. They are called as statements, not in expressions:\n  logMessage(\"starting\")        // CORRECT — standalone call\n  sendAlert(\"warning\")          // CORRECT — standalone call\n\nSee Q597 for function parameters and returns. See Q596 for function vs method.","ek9Example":"defines module qa.operators.voidnotassign\n\n  defines function\n\n    formatEntry() as pure\n      -> msg as String\n      <- rtn as String: `[INFO] ${msg}`\n\n  defines program\n\n    VoidAssignDemo()\n      stdout <- Stdout()\n\n      //Functions with <- return values that CAN be assigned\n      formatted <- formatEntry(\"server started\")\n      stdout.println(formatted)\n\n      //Void calls are standalone statements\n      stdout.println(\"Direct output is a void call\")\n\n      //Multiple return values demonstrated\n      first <- formatEntry(\"step one\")\n      second <- formatEntry(\"step two\")\n      stdout.println(first)\n      stdout.println(second)","migrationContext":"Java: void methods cannot be assigned. C: void functions cannot be assigned. Python: None returned implicitly, assigning prints gives None. Rust: () unit type returned. EK9: void means no return, cannot assign.","keywords":["E10010","assign","effect","function","return","side","value","void"],"primaryTopics":["void assignment error","E10010","void vs returning functions"],"typicalErrors":[{"error":"E10010","correct":"stdout.println(\"Direct output is a void call\")","incorrect":"voidResult <- stdout.println(\"Direct output is a void call\")","explanation":"A void call returns nothing, so its result cannot be assigned to a variable. See ek9 -h E10010 for details."}],"companions":[]}
{"id":942,"category":"Functions and Methods","question":"How do I pass a function as a parameter in EK9?","url":"https://ek9.io/qa/QA0942.html","alternatePhrasings":["What triggers E07480 FUNCTION_DELEGATE_EXPECTED in EK9?","Can functions be first-class values in EK9?","How do I use higher-order functions in EK9?"],"answer":"EK9 supports first-class functions. Define an abstract function as the parameter type, then pass a concrete function that matches its signature.\n\nDEFINE THE FUNCTION TYPE\nAn abstract function acts as a type signature:\n  Transformer() as pure abstract\n    -> incoming as Integer\n    <- rtn as Integer\n\nACCEPT IT AS A PARAMETER\n  applyTransform()\n    ->\n      value as Integer\n      fn as Transformer\n    <- answer as Integer: fn(value)\n\nPASS A MATCHING FUNCTION\n  doubleIt() is Transformer as pure\n    -> incoming as Integer\n    <- rtn as Integer: incoming * 2\n\n  answer <- applyTransform(5, doubleIt)\n\nThe concrete function must match the abstract function's signature exactly. If it does not, the compiler raises E07480.\n\nSTREAMS USE THIS PATTERN\nStream operations like 'filter by' and 'map with' accept function parameters internally. The predicate you pass to filter must match the expected signature.\n\nSee Q597 for function parameters. See Q601 for dynamic functions. See Q596 for function vs method.","ek9Example":"defines module qa.functions.asparameter\n\n  defines function\n\n    Transformer() as pure abstract\n      -> incoming as Integer\n      <- rtn as Integer?\n\n    doubleIt() is Transformer as pure\n      -> incoming as Integer\n      <- rtn as Integer: incoming * 2\n\n    tripleIt() is Transformer as pure\n      -> incoming as Integer\n      <- rtn as Integer: incoming * 3\n\n    applyTransform()\n      ->\n        operand as Integer\n        fn as Transformer\n      <- answer as Integer: fn(operand)\n\n  defines program\n\n    FunctionParamDemo()\n      stdout <- Stdout()\n\n      doubled <- applyTransform(7, doubleIt)\n      stdout.println(`7 doubled: ${doubled}`)\n\n      tripled <- applyTransform(7, tripleIt)\n      stdout.println(`7 tripled: ${tripled}`)","migrationContext":"Java: Functional interfaces (Function, Predicate, Consumer). Python: functions are objects, pass directly. Rust: Fn/FnMut/FnOnce trait bounds. Go: func types as parameters. Kotlin: lambda and function references. EK9: abstract functions as type signatures, concrete functions with 'is' relationship.","keywords":["E07480","abstract","delegate","first-class","function","higher-order","parameter","pass"],"primaryTopics":["function as parameter","E07480","higher-order functions"],"typicalErrors":[{"error":"E06270","correct":"applyTransform(7, doubleIt)","incorrect":"applyTransform(7, \"doubleIt\")","explanation":"Function parameters expect a matching function delegate, not a String literal — pass the function name (doubleIt) without quotes so the argument types match. See ek9 -h E06270 for details."}],"companions":[]}
{"id":943,"category":"Syntax and Structure Rules","question":"Why does EK9 reject empty defines blocks?","url":"https://ek9.io/qa/QA0943.html","alternatePhrasings":["What triggers E01087 in EK9?","Can I have an empty class body in EK9?","Why must every defines section have content?"],"answer":"EK9 does not allow empty defines blocks. Every section header must contain at least one meaningful declaration.\n\nEMPTY BLOCKS ARE ERRORS\n  defines class           // ERROR E01087 — nothing inside\n\n  defines function        // ERROR E01087 — nothing inside\n\nWHY NOT ALLOWED\nEmpty blocks serve no purpose. They indicate incomplete code, placeholder stubs, or copy-paste errors. EK9 treats them as errors because the compiler enforces completeness.\n\nEVERY SECTION NEEDS CONTENT\n  defines class\n    MyClass               // At least one class declaration\n      value <- 0\n\n  defines function\n    doWork()              // At least one function\n      stdout <- Stdout()\n      stdout.println(\"working\")\n\n  defines program\n    Main()                // At least one program\n      stdout <- Stdout()\n      stdout.println(\"running\")\n\nIf you are not ready to fill a section, remove the section header entirely. Add it back when you have content.\n\nSee Q893 for section header requirements. See Q725 for class body ordering.","ek9Example":"defines module qa.syntax.noemptydefines\n\n  defines function\n\n    greet() as pure\n      -> name as String\n      <- rtn as String: `Welcome, ${name}`\n\n  defines class\n\n    Greeter\n      prefix <- \"Hello\"\n\n      Greeter()\n        -> p as String\n        prefix :=: p\n\n      makeGreeting() as pure\n        -> name as String\n        <- rtn as String: `${prefix}, ${name}`\n\n      default operator ?\n\n  defines program\n\n    NoEmptyDefinesDemo()\n      stdout <- Stdout()\n\n      //Every section above has content\n      stdout.println(greet(\"Steve\"))\n\n      greeter <- Greeter(\"Good morning\")\n      stdout.println(greeter.makeGreeting(\"World\"))","migrationContext":"Java: empty class bodies allowed (class Empty {}). Python: pass keyword for empty blocks. Rust: empty impl blocks allowed. Go: empty struct allowed. EK9: no empty blocks — every section must contain declarations.","keywords":["E01087","block","content","defines","empty","required","section","stub"],"primaryTopics":["empty defines error","E01087","section content requirement"],"typicalErrors":[{"error":"E01087","correct":"  defines function\n\n    greet() as pure\n      -> name as String\n      <- rtn as String: `Welcome, ${name}`","incorrect":"  defines function","explanation":"Empty defines blocks are not allowed. Every section header (defines class, defines function, etc.) must contain at least one declaration. Remove the header if you have no content yet. See ek9 -h E01087 for details."}],"companions":[]}
{"id":944,"category":"Web Services","question":"What are the basic rules for EK9 service definitions?","url":"https://ek9.io/qa/QA0944.html","alternatePhrasings":["What triggers E07240 SERVICE_METHOD_NEEDS_BODY?","What triggers E07670 SERVICE_OPERATOR_NOT_SUPPORTED?","What triggers E07740 SERVICE_NON_WEB_METHOD_NOT_ALLOWED?","What operators can services use for CRUD?"],"answer":"EK9 services enforce three fundamental rules:\n\n1. CONCRETE BODIES REQUIRED (E07240)\nEvery service method must have a concrete implementation body. Abstract methods cannot serve HTTP requests.\n\n2. SUPPORTED OPERATORS ONLY (E07670)\nServices support specific operators for CRUD patterns:\n  += for POST (create)\n  -= for DELETE\n  :~: for PATCH (merge)\n  :^: for PUT (replace)\nOther operators like ==, <, + are not valid on services.\n\n3. NO PROTECTED NON-WEB METHODS (E07740)\nAll service methods must be HTTP-bound (GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS). Private helper methods and protected non-web methods are not allowed because services expose only HTTP endpoints.\n\nSee Q657 for URI mapping. See Q659 for HTTPResponse. See Q660 for CRUD operators. See Q685 for method bodies.","ek9Example":"defines module qa.services.basics.overview\n\n  defines class\n\n    <?-\n      Models an HTTP response with status and content.\n      Demonstrates the pattern services use for endpoint handlers.\n    -?>\n    HealthResponse\n      statusCode as Integer: 200\n      responseBody as String: `{\"status\": \"ok\"}`\n      responseType as String: \"application/json\"\n\n      HealthResponse()\n        ->\n          givenStatus as Integer\n          givenBody as String\n        this.statusCode :=: givenStatus\n        this.responseBody :=: givenBody\n\n      statusCode()\n        <- rtn as Integer: Integer(statusCode)\n\n      responseBody()\n        <- rtn as String: String(responseBody)\n\n      responseType()\n        <- rtn as String: String(responseType)\n\n      operator $ as pure\n        <- rtn as String: `HealthResponse[${statusCode}]`\n      operator #^ as pure\n        <- rtn as String: $this\n      default operator ?\n\n    <?-\n      Simulates a service endpoint handler.\n      In real EK9 services, methods must have concrete bodies (E07240).\n    -?>\n    HealthHandler\n      endpoint as String: \"/api/health\"\n\n      handleGet()\n        <- rtn as HealthResponse: HealthResponse(200, `{\"status\": \"ok\"}`)\n\n      endpoint()\n        <- rtn as String: String(endpoint)\n\n      operator $ as pure\n        <- rtn as String: `Handler[${endpoint}]`\n      operator #^ as pure\n        <- rtn as String: $this\n      default operator ?\n\n  defines program\n\n    ServiceBasicsDemo()\n      stdout <- Stdout()\n      handler <- HealthHandler()\n      healthResult <- handler.handleGet()\n\n      stdout.println(\"Service basics:\")\n      stdout.println(\"  Methods must have concrete bodies\")\n      stdout.println(\"  Only CRUD operators: +=, -=, :~:, :^:\")\n      stdout.println(\"  All methods must be HTTP-bound\")\n      stdout.println($healthResult)","migrationContext":"Java Spring: controllers can have private helper methods. Python Flask: handler functions are plain Python. Go: handlers are plain functions. EK9: services are strictly HTTP-endpoint containers with enforced method rules.","keywords":["CRUD","E07240","E07670","E07740","basics","body","method","operator","service","web"],"primaryTopics":["service basics","E07240","E07670","E07740"],"typicalErrors":[],"companions":[]}
{"id":945,"category":"Web Services","question":"How do HTTP verbs, URI path variables, and parameter qualifiers work in EK9 services?","url":"https://ek9.io/qa/QA0945.html","alternatePhrasings":["What triggers E07680 SERVICE_HTTP_VERB_REQUIRED?","What triggers E07690 SERVICE_URI_PATH_REQUIRED?","What triggers E07710 SERVICE_PARAM_NEEDS_QUALIFIER?","What triggers E07720 SERVICE_DUPLICATE_QUALIFIER?","How do I bind path variables to parameters?"],"answer":"EK9 service methods require explicit HTTP configuration:\n\n1. HTTP VERB REQUIRED (E07680)\nEvery method needs an HTTP verb: GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS.\n\n2. URI PATH REQUIRED (E07690)\nMethods must specify a URI path after 'for :/path'.\n\n3. PARAMETER QUALIFIERS (E07710)\nParameters in service methods need qualifiers like PATH, QUERY, HEADER, COOKIE, or REQUEST. Path variables are bound automatically from URI segments.\n\n4. NO DUPLICATE QUALIFIERS (E07720)\nEach parameter qualifier can only be used once per method.\n\nSee Q657 for URI mapping. See Q846 for parameter types. See Q868 for path parameter types.","ek9Example":"defines module qa.services.uripath.params\n\n  defines constant\n\n    JSON_TYPE <- \"application/json\"\n\n  defines class\n\n    <?-\n      Models a user lookup response.\n      Demonstrates how path variables bind to parameters in services.\n    -?>\n    UserResponse\n      userId as Integer: 0\n      contentKind as String: JSON_TYPE\n\n      UserResponse()\n        -> givenUserId as Integer\n        this.userId :=: givenUserId\n\n      userId()\n        <- rtn as Integer: Integer(userId)\n\n      contentKind()\n        <- rtn as String: String(contentKind)\n\n      toJson()\n        <- rtn as String: `{\"userId\": ${userId}}`\n\n      operator $ as pure\n        <- rtn as String: `UserResponse[${userId}]`\n      operator #^ as pure\n        <- rtn as String: $this\n      default operator ?\n\n    <?-\n      Models a user list response.\n    -?>\n    UserListResponse\n      userCount as Integer: 0\n\n      UserListResponse()\n        -> givenCount as Integer\n        this.userCount :=: givenCount\n\n      toJson()\n        <- rtn as String: `{\"users\": [], \"count\": ${userCount}}`\n\n      operator $ as pure\n        <- rtn as String: `UserListResponse[${userCount}]`\n      operator #^ as pure\n        <- rtn as String: $this\n      default operator ?\n\n    <?-\n      Simulates a user service with path parameter binding.\n      In real services, GET /users/{userId} binds userId from the URI.\n    -?>\n    UserEndpoint\n\n      findUser()\n        -> userId as Integer\n        <- rtn as UserResponse: UserResponse(userId)\n\n      listUsers()\n        <- rtn as UserListResponse: UserListResponse(0)\n\n      operator $ as pure\n        <- rtn as String: \"UserEndpoint\"\n      operator #^ as pure\n        <- rtn as String: $this\n      default operator ?\n\n  defines program\n\n    ServiceUriParamsDemo()\n      stdout <- Stdout()\n      endpoint <- UserEndpoint()\n      userResult <- endpoint.findUser(42)\n      listResult <- endpoint.listUsers()\n\n      stdout.println(\"Service URI parameters:\")\n      stdout.println(\"  GET /users/{userId} -> path variable binding\")\n      stdout.println(\"  GET /users/list -> no parameters\")\n      stdout.println(userResult.toJson())\n      stdout.println(listResult.toJson())","migrationContext":"Java Spring: @GetMapping, @PathVariable, @RequestParam. Python Flask: @app.route with <param>. Go: mux.Vars(r) for path variables. EK9: HTTP verb and URI path required in method signature, parameters bound from path segments.","keywords":["E07680","E07690","E07710","E07720","HTTP","URI","parameter","path","service","verb"],"primaryTopics":["service URI params","E07680","E07690","E07710","E07720"],"typicalErrors":[],"companions":[]}
{"id":946,"category":"Web Services","question":"How must path variable counts match parameters in EK9 services?","url":"https://ek9.io/qa/QA0946.html","alternatePhrasings":["What triggers E07730 SERVICE_PATH_PARAM_COUNT_MISMATCH?","What triggers E07770 SERVICE_HTTPREQUEST_ONLY_WITH_REQUEST?","What triggers E07780 SERVICE_HTTPREQUEST_AT_MOST_ONE?","How do I use HTTPRequest in a service method?"],"answer":"EK9 validates service method signatures for path consistency:\n\n1. PATH PARAM COUNT MUST MATCH (E07730)\nThe number of {variables} in the URI must equal the number of parameters declared on the method.\n  findItem() as GET for :/{catId}/{itemId}\n    -> catId as Integer\n    -> itemId as Integer\nTwo path variables, two parameters — correct.\n\n2. HTTPREQUEST RULES (E07770, E07780)\nHTTPRequest can only be used with :=: REQUEST binding, not as a path/query parameter. At most one HTTPRequest parameter per method.\n\nSee Q657 for URI mapping. See Q846 for parameter type rules. See Q868 for path parameter types.","ek9Example":"defines module qa.services.path.matching\n\n  defines constant\n\n    JSON_CONTENT <- \"application/json\"\n\n  defines class\n\n    <?-\n      Models a catalog item found by category and item identifiers.\n      Demonstrates how multi-segment path variables map to parameters.\n    -?>\n    CatalogItem\n      categoryId as Integer: 0\n      itemId as Integer: 0\n      contentKind as String: JSON_CONTENT\n\n      CatalogItem()\n        ->\n          givenCategoryId as Integer\n          givenItemId as Integer\n        this.categoryId :=: givenCategoryId\n        this.itemId :=: givenItemId\n\n      categoryId()\n        <- rtn as Integer: Integer(categoryId)\n\n      itemId()\n        <- rtn as Integer: Integer(itemId)\n\n      contentKind()\n        <- rtn as String: String(contentKind)\n\n      toJson()\n        <- rtn as String: `{\"cat\": ${categoryId}, \"item\": ${itemId}}`\n\n      operator $ as pure\n        <- rtn as String: `CatalogItem[${categoryId}, ${itemId}]`\n      operator #^ as pure\n        <- rtn as String: $this\n      default operator ?\n\n    <?-\n      Simulates a catalog endpoint with multi-segment path binding.\n      In real services: GET /catalog/{catId}/{itemId} requires two parameters.\n      The compiler validates path variable count matches parameter count (E07730).\n    -?>\n    CatalogEndpoint\n\n      findItem()\n        ->\n          catId as Integer\n          itemId as Integer\n        <- rtn as CatalogItem: CatalogItem(catId, itemId)\n\n      operator $ as pure\n        <- rtn as String: \"CatalogEndpoint\"\n      operator #^ as pure\n        <- rtn as String: $this\n      default operator ?\n\n  defines program\n\n    ServicePathMatchingDemo()\n      stdout <- Stdout()\n      endpoint <- CatalogEndpoint()\n      catalogResult <- endpoint.findItem(10, 25)\n\n      stdout.println(\"Path matching rules:\")\n      stdout.println(\"  URI variables must match parameter count\")\n      stdout.println(\"  GET /catalog/{catId}/{itemId} -> 2 params\")\n      stdout.println(catalogResult.toJson())","migrationContext":"Java Spring: @PathVariable count must match. Python Flask: <param> count must match function args. Go: manual extraction from URL. EK9: compile-time validation that URI variable count matches parameter count.","keywords":["E07730","E07770","E07780","HTTPRequest","count","match","parameter","path","service","variable"],"primaryTopics":["service path matching","E07730","E07770","E07780"],"typicalErrors":[],"companions":[]}
{"id":947,"category":"Syntax and Structure Rules","question":"Which types are allowed for typed program arguments in EK9?","url":"https://ek9.io/qa/QA0947.html","alternatePhrasings":["What triggers E07600 PROGRAM_ARGUMENT_TYPE_NOT_SUPPORTED?","Why can't I use custom types as program arguments?","What types can a program accept on the command line?"],"answer":"Program arguments support: String, Integer, Float, Boolean, Character. NOT custom types.\n\nEK9 programs with typed parameters (style 2) can only use types that the runtime can parse from command-line strings. Custom classes, records, and complex types cannot be automatically parsed.\n\nSUPPORTED TYPES\nString, Integer, Float, Boolean, Date, DateTime, Duration, Millisecond, Colour, Dimension, Money, Path, RegularExpression, Character.\n\nUNSUPPORTED (E07600)\nCustom classes, records, traits, enumerations, List, Dict, Optional, or any user-defined type.\n\nWORKAROUND\nUse String parameters and parse manually:\n  MyProgram()\n    -> configPath as String\n    // Parse configPath into your custom type\n\nSee Q889 for program argument styles. See Q888 for program return type.","ek9Example":"defines module qa.syntax.programargtypes\n\n  defines program\n\n    <?-\n      Program with typed arguments.\n      Only built-in parseable types are allowed.\n    -?>\n    ProgramArgumentTypesDemo()\n      ->\n        name as String\n        age as Integer\n        verbose as Boolean\n\n      stdout <- Stdout()\n      stdout.println(`Name: ${name}`)\n      stdout.println(`Age: ${age}`)\n      stdout.println(`Verbose: ${verbose}`)","migrationContext":"Java: main(String[]) only. Python: sys.argv strings. Go: os.Args strings. Rust: std::env::args strings. EK9: compiler-parsed typed parameters from built-in parseable types only.","keywords":["Boolean","E07600","Float","Integer","String","argument","custom","program","supported","type"],"primaryTopics":["program argument types","E07600","PROGRAM_ARGUMENT_TYPE_NOT_SUPPORTED"],"typicalErrors":[{"error":"E07600","correct":"        name as String","incorrect":"        name as GUID","explanation":"Program arguments are limited to a finite set of parseable built-in types; GUID (like any custom or unsupported type) is rejected with E07600 - use String and parse manually. See ek9 -h E07600 for details."}],"companions":[]}
{"id":948,"category":"Generics","question":"Why must generic types always be parameterized when used?","url":"https://ek9.io/qa/QA0948.html","alternatePhrasings":["What triggers E04080 PARAMETERIZATION_REQUIRED?","Why can't I use bare List without 'of String'?","How do I correctly use Dict with type parameters?"],"answer":"List needs 'of String', Dict needs 'of (String, Integer)'. Can't use bare generic name.\n\nEK9 requires all generic types to be fully parameterized when used. A bare generic name like 'List' or 'Dict' without type arguments triggers E04080.\n\nCORRECT USAGE\n  names <- List() of String\n  mapping <- Dict() of (String, Integer)\n  maybe <- Optional() of Float\n\nINCORRECT USAGE (E04080)\n  names <- List()          // ERROR: needs 'of String'\n  mapping <- Dict()        // ERROR: needs 'of (K, V)'\n\nWHY REQUIRED\nWithout type parameters, the compiler cannot verify type safety of elements added to or retrieved from the collection. Unlike Java's raw types, EK9 never allows unparameterized generics.\n\nSee Q654 for parameter count validation. See Q196 for multi-parameter generics. See Q642 for constructor inference.","ek9Example":"defines module qa.generics.paramsrequired\n\n  defines program\n\n    GenericParamsRequiredDemo()\n      stdout <- Stdout()\n\n      //Correctly parameterized generics\n      names <- List() of String\n      names += \"Alice\"\n      names += \"Bob\"\n      stdout.println(`Names count: ${length names}`)\n\n      ages <- Dict() of (String, Integer)\n      stdout.println(`Dict set: ${ages?}`)\n\n      maybe <- Optional() of Float\n      stdout.println(`Optional set: ${maybe?}`)\n\n      //Constructor inference also works\n      inferred <- List(\"one\")\n      stdout.println(`Inferred list count: ${length inferred}`)","migrationContext":"Java: raw types allowed but deprecated (List vs List<String>). Python: untyped collections common. Go: generics always parameterized (Go 1.18+). Rust: generics always parameterized. EK9: E04080 enforces parameterization, no raw types ever.","keywords":["Dict","E04080","List","generic","parameter","parameterization","raw","required","type"],"primaryTopics":["generic parameterization required","E04080","PARAMETERIZATION_REQUIRED"],"typicalErrors":[{"error":"E06010","correct":"names <- List() of String","incorrect":"names <- List()","explanation":"Generic types must be parameterized. List requires 'of String' (or another type). Using a bare generic name without type parameters triggers E06010. See ek9 -h E06010 for details."}],"companions":[]}
{"id":949,"category":"Operators and Expressions","question":"What are the rules for using 'default operator' in EK9?","url":"https://ek9.io/qa/QA0949.html","alternatePhrasings":["What triggers E07190 DEFAULT_OPERATOR_IN_EXTENDING_CLASS?","What triggers E07200 PROPERTY_TYPE_MISSING_OPERATOR?","Can I use default operator in a class that extends another?","Why do my properties need operators for default to work?"],"answer":"default operator rules: can't use in extending classes if parent has it. Property types must support required operators.\n\n1. EXTENDING CLASS RESTRICTION (E07190)\nIf a parent class already uses 'default operator' for a given operator, the extending class cannot also use 'default operator' for the same operator. Override it explicitly instead.\n\n2. PROPERTY TYPE REQUIREMENTS (E07200)\nWhen you use 'default operator ==', all property types in the class must support ==. If any property's type lacks ==, compilation fails with E07200.\n\nDEFAULT OPERATOR BEHAVIOR\n'default operator' generates field-by-field implementations:\n  default operator == compares all fields\n  default operator $ concatenates field string representations\n  default operator ? checks all fields are set\n\nSee Q116 for default operator syntax. See Q245 for custom operators.","ek9Example":"defines module qa.operators.defaultrules\n\n  defines class\n\n    <?-\n      Class using default operators.\n      All property types (String, Integer) support ==, $, ?.\n    -?>\n    Person\n      name <- String()\n      age <- Integer()\n\n      default private Person() as pure\n\n      Person() as pure\n        ->\n          name as String\n          age as Integer\n        this.name :=: name\n        this.age :=: age\n\n      //default generates field-by-field comparison\n      default operator <=>\n      default operator ==\n      default operator <>\n      default operator $\n      default operator #?\n      default operator :=:\n\n      override operator ? as pure\n        <- rtn as Boolean: name? and age?\n\n  defines program\n\n    DefaultOperatorRulesDemo()\n      stdout <- Stdout()\n\n      p1 <- Person(\"Alice\", 30)\n      p2 <- Person(\"Alice\", 30)\n      p3 <- Person(\"Bob\", 25)\n\n      stdout.println(`p1 == p2: ${p1 == p2}`)\n      stdout.println(`p1 == p3: ${p1 == p3}`)\n      stdout.println(`p1: ${p1}`)\n      stdout.println(`p1 set: ${p1?}`)","migrationContext":"Java: IDE-generated equals/hashCode. Python: dataclass auto-generates. Rust: derive(Eq, Hash). Kotlin: data class auto-generates. EK9: 'default operator' with compile-time validation that property types support the required operator.","keywords":["E07190","E07200","auto","default","extending","field","generate","operator","property","type"],"primaryTopics":["default operator rules","E07190","E07200"],"typicalErrors":[{"error":"E02030","correct":"default operator ==","incorrect":"default operator <=>","explanation":"The class already has 'default operator <=>' defined. Changing == to <=> creates a duplicate operator definition, triggering E02030. Each operator can only be defined once. See ek9 -h E02030 for details."}],"companions":[]}
{"id":950,"category":"Security and Sanitization","question":"Where can the 'sanitized' keyword NOT be used in EK9?","url":"https://ek9.io/qa/QA0950.html","alternatePhrasings":["What triggers E07941 SANITIZED_NOT_ON_CAPTURED?","What triggers E07943 SANITIZED_NOT_ON_DECLARATION?","Why can't captured variables be sanitized?","Where is the sanitized modifier restricted?"],"answer":"sanitized keyword can't be on captured variables or declarations.\n\n1. NOT ON CAPTURES (E07941)\nWhen a dynamic function or class captures a variable, the capture cannot be marked sanitized. Sanitization is a property of function parameters, not captures.\n\n2. NOT ON DECLARATIONS (E07943)\nLocal variable declarations cannot use sanitized. Only function/method parameters can be marked sanitized because sanitized tracks external untrusted input entering a function boundary.\n\nVALID USAGE\n  processInput() as pure\n    -> input as sanitized String\n    <- result as String: String(input)\n\nINVALID USAGE\n  localVar as sanitized String    // E07943\n\nWHY RESTRICTED\nSanitization is about trust boundaries. Only parameters represent data crossing a trust boundary. Local variables and captures are already inside the trusted zone.\n\nSee Q215 for sanitized parameter basics. See Q217 for pure interaction. See Q269 for input validation.","ek9Example":"defines module qa.security.sanitizedrestrictions\n\n  defines function\n\n    <?-\n      Valid use: sanitized on function parameter.\n    -?>\n    validateInput() as pure\n      -> userInput as sanitized String\n      <- result as String?\n\n      safeCopy <- String(userInput)\n      result: \"Validated: \" + safeCopy\n\n    processQuery() as pure\n      -> sql as sanitized String\n      <- output as String?\n\n      localCopy <- String(sql)\n      output: \"Query: \" + localCopy\n\n  defines program\n\n    SanitizedRestrictionsDemo()\n      stdout <- Stdout()\n\n      r1 <- validateInput(\"user data\")\n      if r1?\n        stdout.println(r1)\n\n      r2 <- processQuery(\"SELECT 1\")\n      if r2?\n        stdout.println(r2)\n\n      stdout.println(\"sanitized only on function parameters\")","migrationContext":"Java: no language-level sanitization. Python: no taint tracking. Rust: newtype for manual tracking. EK9: sanitized restricted to function parameters only, enforced at compile time.","keywords":["E07941","E07943","boundary","captured","declaration","parameter","restriction","sanitized","security","trust"],"primaryTopics":["sanitized restrictions","E07941","E07943"],"typicalErrors":[{"error":"E07920","correct":"      <- result as String?","incorrect":"      <- result as sanitized String?","explanation":"The sanitized modifier is only valid on incoming function/method parameters, not on a return or local declaration. See ek9 -h E07920 for details."}],"companions":[]}
{"id":951,"category":"Advanced Type System","question":"What happens when you pass the wrong kind of construct as a parameter?","url":"https://ek9.io/qa/QA0951.html","alternatePhrasings":["What triggers E50090 GENUS_MISMATCH?","Why can't I pass a class where a function is expected?","What is parameter genus in EK9?","How does EK9 validate construct kinds?"],"answer":"Parameter genus (fundamental KIND) must match — can't pass a class where a function is expected.\n\nEK9 validates not just types but the fundamental kind (genus) of each argument. A class, function, record, trait, and service are different genera. Passing one where another is expected triggers E50090.\n\nGENUS CATEGORIES\n  CLASS — instantiated objects with methods\n  FUNCTION — callable units\n  RECORD — data-only aggregates\n  TRAIT — interface contracts\n  SERVICE — HTTP endpoints\n\nEXAMPLE\nIf a function expects a function parameter, passing a class instance fails:\n  doWork()\n    -> action as Assessor of String\n    ...\n\n  doWork(MyClass())    // E50090 if MyClass is not an Assessor\n\nSee Q254 for Any type. See Q258 for type coercion. See Q255 for method resolution.","ek9Example":"defines module qa.types.parametergenus\n\n  defines function\n\n    <?-\n      Function that accepts a predicate.\n    -?>\n    countMatching()\n      ->\n        items as List of String\n        check as Predicate of String\n      <- result as Integer: 0\n\n      for item in items\n        if check(item)\n          result++\n\n  defines program\n\n    ParameterGenusDemo()\n      stdout <- Stdout()\n\n      names <- List() of String\n      names += \"Alice\"\n      names += \"Bob\"\n      names += \"Charlie\"\n\n      //Dynamic function matching Predicate of String\n      minLength <- 4\n\n      longCheck <- (minLen: minLength) is Predicate of String as pure function\n        r: t? and length t >= minLen\n\n      longCount <- countMatching(names, longCheck)\n      stdout.println(`Long names: ${longCount}`)\n\n      shortCheck <- (maxLen: minLength) is Predicate of String as pure function\n        r: t? and length t < maxLen\n\n      shortCount <- countMatching(names, shortCheck)\n      stdout.println(`Short names: ${shortCount}`)","migrationContext":"Java: interface vs class distinction at compile time. Python: duck typing, no genus check. Rust: trait vs struct distinction. Go: interface vs struct. EK9: genus-level validation ensures fundamental construct kind matches.","keywords":["E50090","class","construct","function","genus","kind","mismatch","parameter","record","trait"],"primaryTopics":["parameter genus","E50090","GENUS_MISMATCH"],"typicalErrors":[{"error":"E06270","correct":"countMatching(names, longCheck)","incorrect":"countMatching(names, stdout)","explanation":"The parameter expects a Predicate (function genus), not a Stdout instance. Passing the wrong kind of construct as a parameter causes a parameter mismatch. See ek9 -h E06270 for details."}],"companions":[]}
{"id":952,"category":"Syntax and Structure Rules","question":"Why must EK9 constructs be used in their valid context?","url":"https://ek9.io/qa/QA0952.html","alternatePhrasings":["What triggers E09010 CONSTRUCT_IN_WRONG_CONTEXT?","Why can't I define a class inside a function?","Where can I place different construct types?"],"answer":"Language constructs must be used in their valid context.\n\nEK9 enforces that constructs appear only where they are valid:\n\nVALID CONTEXTS\n  'defines class' — inside module, contains classes\n  'defines function' — inside module, contains functions\n  'defines record' — inside module, contains records\n  'defines trait' — inside module, contains traits\n  'defines service' — inside module, contains services\n  'defines program' — inside module, contains programs\n  'defines constant' — inside module, contains constants\n  'defines application' — inside module, contains applications\n\nSTRUCTURE\nEvery .ek9 file starts with 'defines module <name>' and contains one or more 'defines <section>' blocks. Each section can only contain its declared construct type.\n\nSee Q725 for class body ordering. See Q726 for module reference syntax. See Q893 for section headers.","ek9Example":"defines module qa.syntax.wrongcontext\n\n  defines class\n\n    <?-\n      Classes go in 'defines class' section.\n    -?>\n    Point\n      x <- Float()\n      y <- Float()\n\n      default private Point() as pure\n\n      Point() as pure\n        ->\n          x as Float\n          y as Float\n        this.x :=: x\n        this.y :=: y\n\n      default operator <=>\n      default operator ==\n      default operator $\n      default operator :=:\n\n      override operator ? as pure\n        <- rtn as Boolean: x? and y?\n\n  defines program\n\n    ConstructContextDemo()\n      stdout <- Stdout()\n\n      p1 <- Point(3.0, 4.0)\n      p2 <- Point(3.0, 4.0)\n\n      stdout.println(`p1: ${p1}`)\n      stdout.println(`p1 == p2: ${p1 == p2}`)\n      stdout.println(\"Each section holds its own construct type\")","migrationContext":"Java: class inside method is allowed (local class). Python: class inside function allowed. Rust: impl blocks must be at module level. Go: type definitions at package level. EK9: strict section-based structure, each section contains only its declared construct type.","keywords":["E09010","class","construct","context","function","module","placement","record","section","structure"],"primaryTopics":["construct context rules","E09010","CONSTRUCT_IN_WRONG_CONTEXT"],"typicalErrors":[{"error":"E09010","correct":"stdout.println(`p1: ${p1}`)","incorrect":"stdout.println(Point)","explanation":"Using a type name like 'Point' directly as a value expression is inappropriate. Types are not values. See ek9 -h E09010 for details."}],"companions":[]}
{"id":953,"category":"Testing","question":"How do @Test programs work with command-line arguments, timeouts, and expected output?","url":"https://ek9.io/qa/QA0953.html","alternatePhrasings":["What triggers E81001 MISSING_EXPECTED_OUTPUT?","What triggers E81002 ORPHAN_EXPECTED_OUTPUT?","What triggers E81003 ARGUMENT_COUNT_MISMATCH?","What triggers E81004 UNEXPECTED_ARGUMENTS?","What triggers E81005 MISSING_ARGUMENTS?","What triggers E81006 TYPE_CONVERSION_ERROR?","What triggers E81007 EMPTY_TEST?","What triggers E81008 DUPLICATE_CASE_ID?","What triggers E82001 TEST_TIMEOUT?","What triggers E82002 NO_OUTPUT?","Can @Test programs accept typed parameters?","How do I pass arguments to test programs?"],"answer":"@Test programs accept TYPED PARAMETERS (not just String) — the EK9 runtime introspects the program's parameter signature and parses commandline_arg lines into the declared types. The E81xxx codes flag config issues (paired-file naming, arg counts, type conversion); the E82xxx codes flag execution issues (timeout, no-output).\n\nTEST PROGRAM SIGNATURES\nTests ARE programs. They CAN have typed parameters:\n  @Test\n  NumberProcessor()\n    ->\n      num1 as Integer\n      num2 as Integer\n    assert num1?\n    assert num2?\n\nThe runtime auto-parses each commandline_arg line into the declared type — Integer, Date, Duration, DateTime, Money, Boolean, etc. Per parameterizedHybrid.ek9 corpus example.\n\nCOMPANION FILES (live in test directory)\n  expected_output.txt — single-case stdout (parameter-less test)\n  expected_output_t1.txt / _t2.txt — numbered multi-case stdout (at PACKAGE ROOT)\n  commandline_arg_<name>.txt + expected_case_<name>.txt — named-case pair (INSIDE dev/)\n  env_vars.txt — KEY=VALUE environment\n  expected_stderr.txt — expected stderr (supports {{DateTime}} / {{Millisecond}})\n  test_data.txt — auxiliary input data\n\nE81xxx CONFIG ERRORS (per TestConfigurationError.java)\n  E81001 MISSING_EXPECTED_OUTPUT — commandline_arg_X.txt without expected_case_X.txt\n  E81002 ORPHAN_EXPECTED_OUTPUT — expected_case_X.txt without commandline_arg_X.txt\n  E81003 ARGUMENT_COUNT_MISMATCH — program declares N params, file provides not-N args\n  E81004 UNEXPECTED_ARGUMENTS — commandline file exists but program has no params\n  E81005 MISSING_ARGUMENTS — program declares params but no commandline files found\n  E81006 TYPE_CONVERSION_ERROR — string can't parse as declared parameter type\n  E81007 EMPTY_TEST — @Test program has no asserts AND no expected_output\n  E81008 DUPLICATE_CASE_ID — same case name used twice in one directory\n  E81009 INVALID_FILE_PATTERN — typo in filename (fuzzy-match suggestion)\n  E81010 INVALID_PLACEHOLDER — bad {{...}} syntax in expected output\n  E81013 ENVVARS_PARSE_ERROR — malformed line in env_vars.txt (no =)\n  E81014 ORPHAN_ENVVARS — env_vars file without matching commandline_arg\n  E81016 ORPHAN_STDIN — orphan stdin file\n\nE82xxx EXECUTION ERRORS\n  E82001 TEST_TIMEOUT — execution exceeded timeout (default 30s, EK9_TEST_TIMEOUT_MS configurable)\n  E82002 NO_OUTPUT — test ran but produced no stdout despite expected_output.txt\n\nSTDOUT MISMATCH\nNote: stdout-content-doesn't-match-expected is NOT a numbered E81xx error. It's reported in the test runner's pass/fail report.\n\nSee Q1296 for capture isolation. See Q204 for black-box patterns. See Q205 for parameterized tests.","ek9Example":"defines module qa.testing.programargs\n\n  defines program\n\n    <?-\n      Demonstrates the @Test program pattern with typed parameters\n      and the companion-file model. In a real test, prefix with @Test.\n      Programs (and tests) accept typed parameters that the EK9 runtime\n      auto-parses from commandline_arg files.\n    -?>\n    TestProgramArgsDemo()\n      stdout <- Stdout()\n\n      stdout.println(\"@Test program facts:\")\n      stdout.println(\"  Tests ARE programs (defines program + @Test directive)\")\n      stdout.println(\"  Typed parameters supported (Integer, Date, Money, etc.)\")\n      stdout.println(\"  Runtime auto-parses commandline_arg lines into types\")\n      stdout.println(\"  E81xxx codes flag config issues (paired files, arg counts, type conversion)\")\n      stdout.println(\"  E82001 = TEST_TIMEOUT (default 30s)\")\n      stdout.println(\"  E82002 = NO_OUTPUT (test ran but produced no stdout)\")","migrationContext":"Java JUnit: @Test annotation, @ParameterizedTest with @ValueSource for typed params. Python pytest: fixtures + parametrize for typed params. Go: testing.T with table-driven tests. Rust: #[test] with proptest for parameterised. EK9: @Test directive on a program that accepts typed parameters; the runtime auto-parses commandline_arg files into the declared types, no manual parsing or annotations needed.","keywords":["E81001","E81002","E81003","E81004","E81005","E81006","E81007","E81008","E82001","E82002","argument","commandline_arg","env_vars","expected","expected_case","output","program","test","timeout","typed parameters"],"primaryTopics":["test program args","typed parameters","companion files","E81xxx config errors","E82xxx execution errors"],"typicalErrors":[],"companions":[]}
{"id":954,"category":"Streams and Pipelines","question":"How do I use collect as to accumulate stream items into a custom type in EK9?","url":"https://ek9.io/qa/QA0954.html","alternatePhrasings":["How does collect as work with custom types in EK9?","What does my type need to work with collect as?","What triggers E07835 COLLECT_REQUIRES_DEFAULT_CONSTRUCTOR?"],"answer":"The 'collect as Type' stream terminal accumulates pipeline items into a custom type instance.\n\nYour type needs TWO things:\n  1. A default constructor (no arguments) to create the initial empty accumulator\n  2. An 'operator |' that accepts the pipeline element type to receive each item\n\nHOW IT WORKS\nThe stream pipeline:\n  result <- cat [1, 2, 3] | collect as Counter\n\nIs equivalent to:\n  collector <- Counter()         Step 1: default constructor\n  collector | 1                  Step 2: pipe each item\n  collector | 2\n  collector | 3\n  result <- collector            Step 3: return accumulator\n\nUSING A RECORD\nRecords are ideal for collect-as because field initializers auto-generate a default constructor:\n  Counter\n    count as Integer: 0\n    operator |\n      -> item as Integer\n      count++\n\nWITH FOR-RANGE\nCombine with for-range to count or sum ranges:\n  total <- for i in 1 ... 10 | collect as Counter\n\nIf your type only has parameterized constructors (no default), the compiler reports E07835.\n\nSee Q235 for all stream operations. See Q931 for tail and skip. See Q122 for collect into built-in types.","ek9Example":"defines module qa.streams.collectas\n\n  defines record\n\n    <?-\n      A simple counter that accumulates Integer items from a stream.\n      Has a default constructor (via field initializer) and operator |.\n    -?>\n    Counter\n      count as Integer: 0\n\n      operator |\n        -> item as Integer\n        if item?\n          count++\n\n      operator $ as pure\n        <- rtn as String: String()\n        if count?\n          rtn :=? $count\n\n      default operator ?\n\n  defines trait\n\n    <?-\n      A trait has no constructors — cannot be used with 'collect as' (E07835).\n    -?>\n    Accumulator\n      operator |\n        -> item as Integer\n\n  defines program\n\n    CollectAsDemo()\n      stdout <- Stdout()\n\n      //Collect integers into a Counter using 'collect as'\n      result <- cat [1, 2, 3, 4, 5] | collect as Counter\n      stdout.println(`Count: ${result}`)\n\n      //Works with for-range too\n      rangeResult <- for i in 1 ... 10 | collect as Counter\n      stdout.println(`Range count: ${rangeResult}`)","migrationContext":"Java: Stream.collect(Collector) with Supplier/Accumulator/Combiner. Python: functools.reduce(). Rust: Iterator::fold() or collect() with FromIterator. Go: manual loop accumulation. EK9: collect as Type with default constructor + operator | pattern.","keywords":["E07835","accumulate","aggregate","collect","collect as","counter","custom type","default constructor","fold","operator pipe","record","reduce","stream"],"primaryTopics":["collect as custom type","stream accumulation","E07835"],"typicalErrors":[{"error":"E07835","correct":"collect as Counter","incorrect":"collect as Accumulator","explanation":"The collect-as pattern needs to create an initial empty instance via the default constructor. If your type only has parameterized constructors, add a default constructor or use a record with field initializers. See ek9 -h E07835 for details."}],"companions":[]}
{"id":955,"category":"Classes and OOP","question":"Why does EK9 report E06260 when defining package properties?","url":"https://ek9.io/qa/QA0955.html","alternatePhrasings":["What triggers E06260 PARAMETER_MISMATCH in packages?","Why must package version be a VersionNumber type?","How do I correctly declare package properties?"],"answer":"Package properties in EK9 have strict type requirements. The 'version' property must be a VersionNumber literal, 'description' and 'license' must be Strings, and 'publicAccess' must be Boolean. Using the wrong type triggers E06260.\n\nPACKAGE PROPERTY TYPES\n  version <- 1.0.0-0           // VersionNumber literal\n  description <- \"My package\"  // String literal\n  license <- \"MIT\"             // String literal\n  publicAccess <- true          // Boolean literal\n\nWHY ENFORCED\nPackage metadata is used by the build system and dependency management. Invalid types would break version resolution, licensing, and publication. The compiler validates each property type at parse time.\n\nSee Q10 for module structure. See Q852 for reserved module names. See Q14 for package organization.","ek9Example":"defines module qa.classes.paramtypemismatch\n\n  defines package\n\n    version <- 1.0.0-0\n    description <- \"Demonstrates correct package property types\"\n\n  defines program\n\n    PackagePropertyDemo()\n      stdout <- Stdout()\n      stdout.println(\"Package properties use strict types\")\n      stdout.println(\"version: VersionNumber, description: String\")","migrationContext":"Java: pom.xml version is a free-form string. Python: setup.py version is a string. Rust: Cargo.toml version uses semver strings. Go: go.mod uses semver tags. EK9: version is a typed VersionNumber literal validated at compile time, E06260 if wrong type.","keywords":["E06260","VersionNumber","build","metadata","mismatch","package","parameter","property","type","version"],"primaryTopics":["package properties","E06260","PARAMETER_MISMATCH"],"typicalErrors":[],"companions":[]}
{"id":956,"category":"Advanced Type System","question":"Why can't I constrain a Boolean type in EK9?","url":"https://ek9.io/qa/QA0956.html","alternatePhrasings":["What triggers E04010 TYPE_CANNOT_BE_CONSTRAINED?","Which types cannot be constrained in EK9?","Why does constraining Boolean cause a compiler error?"],"answer":"Not all types can be constrained in EK9. Boolean already has exactly two values (true/false), making constraints meaningless. JSON has dynamic structure with undefined comparison semantics. Abstract types, functions, and traits cannot be instantiated as values.\n\nNON-CONSTRAINABLE TYPES (E04010)\n  Boolean — already maximally constrained (two values)\n  JSON — dynamic structure, no defined comparison\n  Abstract classes — cannot be instantiated\n  Traits — cannot be instantiated\n  Functions — not value types\n\nCONSTRAINABLE TYPES\n  String — constrain with regex or equality\n  Integer — constrain with range (> 0 and < 100)\n  Float — constrain with range\n  Date, Time, DateTime — constrain with range\n  Money — constrain with range\n  Colour — constrain with specific values\n  Records/Classes with comparison operators\n\nSee Q720 for constrainable types. See Q257 for constrained type overview. See Q721 for constrained types as parameters.","ek9Example":"defines module qa.advancedtypes.constraintinvalid\n\n  defines type\n\n    ValidAge as Integer constrain as\n      > 0 and < 150\n\n    ShortName as String constrain as\n      matches /^[A-Za-z]{1,20}$/\n\n  defines program\n\n    TypeConstraintDemo()\n      stdout <- Stdout()\n\n      if validAge <- ValidAge(25)\n        stdout.println(`Valid age: ${validAge}`)\n\n      tooOldValue <- 200\n      badAge <- ValidAge(tooOldValue)\n      stdout.println(`Bad age set: ${badAge?}`)\n\n      if name <- ShortName(\"Alice\")\n        stdout.println(`Valid name: ${name}`)","migrationContext":"Java: no built-in type constraints, use Bean Validation annotations at runtime. Python: runtime-only with pydantic. Rust: newtype pattern with constructor validation. Ada: subtype constraints on scalar types. EK9: compile-time constraint syntax with E04010 for inappropriate types.","keywords":["Boolean","E04010","JSON","abstract","constrain","constraint","invalid","range","type","value"],"primaryTopics":["type constraints","E04010","TYPE_CANNOT_BE_CONSTRAINED"],"typicalErrors":[{"error":"E04010","correct":"    ValidAge as Integer constrain as","incorrect":"    ValidAge as Boolean constrain as","explanation":"Boolean cannot be constrained because it already has exactly two values. Use Integer, String, or other constrainable types. See ek9 -h E04010 for details."}],"companions":[]}
{"id":957,"category":"Dependency Injection","question":"Why can I only inject components, not classes, in EK9?","url":"https://ek9.io/qa/QA0957.html","alternatePhrasings":["What triggers E08160 COMPONENT_INJECTION_NOT_POSSIBLE?","Why does injection with ! fail for class types?","What types support dependency injection in EK9?"],"answer":"EK9 dependency injection (the '!' suffix) only works with abstract component types. Classes, records, functions, and traits cannot be injected. E08160 is raised when you attempt to inject a non-component type.\n\nWHY COMPONENTS ONLY\nComponents are EK9's managed service layer. The DI container knows how to create, wire, and lifecycle-manage components. Classes are user-constructed objects — the container has no way to manage their lifecycle.\n\nINJECTABLE (component genus)\n  defines component\n    Logger as abstract\n      log() as abstract -> message as String\n\nNOT INJECTABLE (E08160)\n  defines class — user-managed objects\n  defines record — data-only aggregates\n  defines trait — interface contracts\n  defines function — callable units\n\nCORRECT PATTERN\n  notifier as NotificationService!    // OK: NotificationService is abstract component\n\nINCORRECT PATTERN\n  helper as HelperClass!              // E08160: HelperClass is a class, not a component\n\nSee Q668 for injectable contexts. See Q111 for component basics. See Q672 for program-application linking.","ek9Example":"defines module qa.di.componentinjectioncontext\n\n  defines class\n\n    <?-\n      A regular class — not a component. Cannot be injected.\n    -?>\n    HelperClass\n      label <- \"helper\"\n\n      default HelperClass()\n\n      getLabel()\n        <- rtn as String: label\n\n      default operator ?\n\n  defines component\n\n    <?-\n      Abstract component — can be injected via DI.\n    -?>\n    NotificationService as abstract\n\n      sendNotification() as abstract\n        -> recipient as String\n\n      default operator ?\n\n    <?-\n      Concrete component implementing the abstract service.\n    -?>\n    EmailNotifier extends NotificationService\n\n      override sendNotification()\n        -> recipient as String\n        stdout <- Stdout()\n        stdout.println(`Email to ${recipient}`)\n\n      default operator ?\n\n  defines application\n\n    NotifyApp\n      register EmailNotifier() as NotificationService\n\n  defines program\n\n    ComponentInjectionDemo() with application of NotifyApp\n      stdout <- Stdout()\n\n      notifier as NotificationService!\n      notifier.sendNotification(\"demo-user\")\n\n      stdout.println(\"Component injection works for abstract components\")","migrationContext":"Java Spring: @Autowired works on any managed bean. Python: inject anywhere. Go: manual wiring. Rust: no DI framework. EK9: injection limited to abstract components for architectural clarity, E08160 if wrong construct genus.","keywords":["DI","E08160","abstract","class","component","construct","genus","injection","managed","service"],"primaryTopics":["component injection","E08160","COMPONENT_INJECTION_NOT_POSSIBLE"],"typicalErrors":[{"error":"E08160","correct":"      notifier as NotificationService!","incorrect":"      notifier as HelperClass!","explanation":"Only abstract component types can be injected with '!'. Classes are not managed by the DI container. Use a component for injectable dependencies. See ek9 -h E08160 for details."}],"companions":[]}
{"id":958,"category":"Code Quality","question":"When should I use 'by' delegation instead of manual method forwarding?","url":"https://ek9.io/qa/QA0958.html","alternatePhrasings":["What triggers E11023 MISSING_BY_DELEGATION?","Why does the compiler complain about manual delegation?","How does EK9 detect redundant delegation boilerplate?"],"answer":"When a class implements a trait without 'by', has a compatible delegate field, and manually overrides 70% or more of the trait methods (minimum 5 methods), the compiler raises E11023.\n\nWHY THIS MATTERS\nManually forwarding every method to a delegate field is boilerplate that the 'by' keyword eliminates. The compiler detects this pattern and enforces cleaner code.\n\nTHRESHOLD\nAll three conditions must be met:\n1. The trait has at least 5 abstract methods\n2. 70% or more are overridden in this class\n3. A compatible field of the trait type exists\n\nTHIS EXAMPLE\nThe TaskHandler class implements Orchestrator (7 abstract methods) and overrides all 7. However, it has NO compatible delegate field of type Orchestrator, so E11023 does not trigger.\n\nThe typicalError mutation adds a delegate field, causing the checker to detect manual forwarding that should use 'by'.\n\nSee Q770 for trait by delegation basics. See Q313 for code smells catalog.","ek9Example":"defines module qa.quality.missingbydelegation\n\n  defines trait\n    Orchestrator\n      checkPreconditions() abstract\n      executeCore() abstract\n      handleCompletion() abstract\n      inspectState() abstract\n      prepareOutput() abstract\n      runCleanup() abstract\n      finalizeResult() abstract\n\n  defines class\n    OrchestratorImpl with trait of Orchestrator\n      override checkPreconditions()\n        require true\n      override executeCore()\n        require true\n      override handleCompletion()\n        require true\n      override inspectState()\n        require true\n      override prepareOutput()\n        require true\n      override runCleanup()\n        require true\n      override finalizeResult()\n        require true\n\n  defines class\n\n    <?-\n      TaskHandler implements Orchestrator with all 7 methods overridden.\n      No compatible delegate field exists, so E11023 does NOT trigger.\n      Adding a delegate field of type Orchestrator would trigger E11023.\n    -?>\n    TaskHandler with trait of Orchestrator\n\n      override checkPreconditions()\n        require true\n      override executeCore()\n        require true\n      override handleCompletion()\n        require true\n      override inspectState()\n        require true\n      override prepareOutput()\n        require true\n      override runCleanup()\n        require true\n      override finalizeResult()\n        require true\n\n      default operator ?\n\n  defines program\n\n    MissingByDelegationDemo()\n      stdout <- Stdout()\n      stdout.println(\"TaskHandler overrides all 7 methods without delegate field\")\n      stdout.println(\"No E11023 because no compatible delegate field exists\")","migrationContext":"Java: no delegation syntax, manual forwarding always allowed. Kotlin: 'by' exists but optional. Go: struct embedding is all-or-nothing. EK9: compiler detects when 'by' should be used and enforces it.","keywords":["E11023","boilerplate","by","clean-code","delegation","forwarding","override","quality","threshold","trait"],"primaryTopics":["by delegation","E11023","MISSING_BY_DELEGATION"],"typicalErrors":[{"error":"E11023","correct":"    TaskHandler with trait of Orchestrator\n\n      override checkPreconditions()\n        require true","incorrect":"    TaskHandler with trait of Orchestrator\n      delegate Orchestrator: OrchestratorImpl()\n\n      override checkPreconditions()\n        delegate.checkPreconditions()","explanation":"Adding a compatible delegate field of type Orchestrator while overriding all 7 methods triggers E11023. The compiler sees 7/7 (100%) overrides with a compatible field and requires 'by delegate'. See ek9 -h E11023 for details."}],"companions":[]}
{"id":959,"category":"Code Quality","question":"What is the hybrid class-component anti-pattern in EK9?","url":"https://ek9.io/qa/QA0959.html","alternatePhrasings":["What triggers E11024 HYBRID_CLASS_COMPONENT?","Why does EK9 reject classes with delegation and many service fields?","How many service fields can a class with 'by' delegation have?"],"answer":"When a class uses 'by' delegation AND has 3 or more additional service fields (trait/abstract typed), it is mixing class and component responsibilities. E11024 triggers.\n\nWHY THIS MATTERS\nA class using 'by' delegation is extending behavior through composition (a class pattern). Having 3+ service fields means it is also coordinating multiple services (a component pattern). Mixing these patterns creates maintenance problems.\n\nTHRESHOLD\n- Must have at least 1 'by' delegation\n- Must have 3 or more additional service-type fields (not counting 'by' delegate fields)\n\nTHIS EXAMPLE\nDocumentManager uses 'by' delegation for Storable trait and has 2 additional service fields (formatter and encoder). With only 2 service fields, this is under the threshold of 3.\n\nThe typicalError mutation adds a third service field, hitting the threshold and triggering E11024.\n\nSee Q314 for cohesion and coupling. See Q111 for component basics.","ek9Example":"defines module qa.quality.excessiveservicefields\n\n  defines trait\n\n    Storable\n      store() abstract\n      retrieve() abstract\n\n    Formatter\n      formatContent() abstract\n\n    Encoder\n      encodeContent() abstract\n\n    Compressor\n      compress() abstract\n\n  defines class\n\n    StorableImpl with trait of Storable\n      override store()\n        require true\n      override retrieve()\n        require true\n\n    PlainFormatter with trait of Formatter\n      override formatContent()\n        require true\n\n    SimpleEncoder with trait of Encoder\n      override encodeContent()\n        require true\n\n    BasicCompressor with trait of Compressor\n      override compress()\n        require true\n\n  defines class\n\n    <?-\n      DocumentManager uses 'by' delegation for Storable\n      and has 2 additional service fields (under threshold of 3).\n      Adding a 3rd service field would trigger E11024.\n    -?>\n    DocumentManager with trait of Storable by persistence\n      persistence Storable: StorableImpl()\n\n      formatter Formatter: PlainFormatter()\n      encoder Encoder: SimpleEncoder()\n\n      title as String: \"untitled\"\n\n      processDocument()\n        formatter.formatContent()\n        encoder.encodeContent()\n        persistence.store()\n\n      default operator ?\n\n  defines program\n\n    ExcessiveServiceFieldsDemo()\n      stdout <- Stdout()\n      stdout.println(\"DocumentManager has 1 by-delegation and 2 service fields\")\n      stdout.println(\"Under the threshold of 3 additional services\")","migrationContext":"Java: no 'by' delegation, no construct distinction. Spring beans mix data and services freely. C#: no built-in delegation detection. Go: no class vs component distinction. EK9: detects hybrid patterns at compile time.","keywords":["E11024","by","class","component","delegation","field","hybrid","quality","service","threshold"],"primaryTopics":["hybrid class-component","E11024","HYBRID_CLASS_COMPONENT"],"typicalErrors":[{"error":"E11024","correct":"      formatter Formatter: PlainFormatter()\n      encoder Encoder: SimpleEncoder()","incorrect":"      formatter Formatter: PlainFormatter()\n      encoder Encoder: SimpleEncoder()\n      compressor Compressor: BasicCompressor()","explanation":"Adding a third service field (compressor) to a class with 'by' delegation triggers E11024 (3 >= threshold of 3). Split into a component for service coordination. See ek9 -h E11024 for details."}],"companions":[]}
{"id":960,"category":"Dependency Injection","question":"Why does EK9 limit injection fields per component to four?","url":"https://ek9.io/qa/QA0960.html","alternatePhrasings":["What triggers E11040 EXCESSIVE_INJECTION_FIELDS?","How many injected dependencies can a component have?","How do I refactor a component with too many injections?"],"answer":"A component with more than 4 injection fields (marked with '!') triggers E11040. This enforces the Single Responsibility Principle based on Clean Architecture research.\n\nWHY FOUR IS THE LIMIT\nResearch from Robert C. Martin (Clean Architecture), Mark Seemann (Dependency Injection in .NET), and Shatnawi et al. (IEEE 2010) demonstrates that components with 5+ dependencies are typically doing too much.\n\nHOW TO FIX\nGroup related dependencies into a facade component:\n- Identify clusters of services used together\n- Create a facade that wraps the cluster\n- Inject the facade instead of individual services\n\nTHIS EXAMPLE\nThe TransactionCoordinator component has 4 injection fields, exactly at the limit. This compiles because 4 is the maximum allowed.\n\nThe typicalError mutation adds a fifth injection field, triggering E11040.\n\nSee Q862 for injection field limit overview. See Q227 for compile-time DI validation.","ek9Example":"defines module qa.di.repeatedinjection\n\n  defines component\n\n    <?-\n      Abstract service contracts for transaction processing.\n    -?>\n    LedgerService as abstract\n      recordEntry() as abstract\n        -> description as String\n      default operator ?\n\n    AuthorizationService as abstract\n      authorize() as abstract\n        -> principal as String\n        <- allowed as Boolean?\n      default operator ?\n\n    MonitoringService as abstract\n      logActivity() as abstract\n        -> activity as String\n      default operator ?\n\n    DispatchService as abstract\n      sendResult() as abstract\n        -> recipient as String\n      default operator ?\n\n    ArchivingService as abstract\n      archiveRecord() as abstract\n        -> recordId as String\n      default operator ?\n\n    <?-\n      Concrete implementations.\n    -?>\n    InMemoryLedger extends LedgerService\n      override recordEntry()\n        -> description as String\n        Stdout().println(\"[LEDGER] \" + description)\n      default operator ?\n\n    SimpleAuthorizer extends AuthorizationService\n      override authorize()\n        -> principal as String\n        <- allowed as Boolean: true\n      default operator ?\n\n    ConsoleMonitor extends MonitoringService\n      override logActivity()\n        -> activity as String\n        Stdout().println(\"[MONITOR] \" + activity)\n      default operator ?\n\n    LocalDispatcher extends DispatchService\n      override sendResult()\n        -> recipient as String\n        Stdout().println(\"[DISPATCH] \" + recipient)\n      default operator ?\n\n    FileArchiver extends ArchivingService\n      override archiveRecord()\n        -> recordId as String\n        Stdout().println(\"[ARCHIVE] \" + recordId)\n      default operator ?\n\n    <?-\n      TransactionCoordinator has exactly 4 injection fields.\n      This is at the limit but valid. Adding a 5th triggers E11040.\n    -?>\n    TransactionCoordinator\n\n      ledger as LedgerService!\n      authorizer as AuthorizationService!\n      monitor as MonitoringService!\n      dispatcher as DispatchService!\n\n      processTransaction()\n        -> txnLabel as String\n        permitted <- authorizer.authorize(txnLabel)\n        if permitted\n          ledger.recordEntry(txnLabel)\n          monitor.logActivity(txnLabel)\n          dispatcher.sendResult(txnLabel)\n\n      default operator ?\n\n  defines application\n\n    TransactionApp\n      register InMemoryLedger() as LedgerService\n      register SimpleAuthorizer() as AuthorizationService\n      register ConsoleMonitor() as MonitoringService\n      register LocalDispatcher() as DispatchService\n\n  defines program\n\n    RepeatedInjectionDemo()\n      stdout <- Stdout()\n      stdout.println(\"TransactionCoordinator has 4 injection fields (at limit)\")\n      stdout.println(\"Adding a 5th would trigger E11040\")","migrationContext":"Java Spring: unlimited @Autowired fields. C#: unlimited DI constructor parameters. Python: no DI framework limits. Go Wire: no limits. EK9: compile-time limit of 4 injection fields per component, enforced by E11040.","keywords":["E11040","SRP","component","dependency","excessive","facade","field","injection","limit","quality"],"primaryTopics":["injection field limit","E11040","EXCESSIVE_INJECTION_FIELDS"],"typicalErrors":[{"error":"E11040","correct":"      ledger as LedgerService!\n      authorizer as AuthorizationService!\n      monitor as MonitoringService!\n      dispatcher as DispatchService!","incorrect":"      ledger as LedgerService!\n      authorizer as AuthorizationService!\n      monitor as MonitoringService!\n      dispatcher as DispatchService!\n      archiver as ArchivingService!","explanation":"5 injection fields exceeds the limit of 4. Extract related services into a facade component and inject the facade instead. See ek9 -h E11040 for details."}],"companions":[]}
{"id":961,"category":"Dependency Injection","question":"What is the maximum number of registrations in an EK9 application block?","url":"https://ek9.io/qa/QA0961.html","alternatePhrasings":["What triggers E11042 EXCESSIVE_APPLICATION_REGISTRATIONS?","Why does EK9 limit application registrations?","How do I split a large application block?"],"answer":"An application block can have at most 12 registrations. Exceeding 12 triggers E11042, indicating the application is becoming a monolith.\n\nWHY TWELVE\nLarge composition roots (the place where all dependencies are wired together) become hard to understand and maintain. Research in component-based software engineering shows that focused, modular composition is more maintainable.\n\nTHE FIX\nSplit into sub-applications using 'with application of':\n  defines application\n    InfrastructureApp\n      register DbService() as AbstractDb\n\n  defines application\n    BusinessApp with application of InfrastructureApp\n      register OrderService() as AbstractOrder\n\nTHIS EXAMPLE\nThe PlatformApp has exactly 11 registrations, safely under the limit of 12. The typicalError mutation adds 2 more registrations to reach 13, triggering E11042.\n\nSee Q892 for application registration limits. See Q227 for DI overview.","ek9Example":"defines module qa.di.excessivemethodinjection\n\n  defines component\n\n    Contract01 as abstract\n      action01() as abstract\n        <- rtn as String?\n      default operator ?\n    Impl01 extends Contract01\n      override action01()\n        <- rtn <- \"c01\"\n      default operator ?\n\n    Contract02 as abstract\n      action02() as abstract\n        <- rtn as String?\n      default operator ?\n    Impl02 extends Contract02\n      override action02()\n        <- rtn <- \"c02\"\n      default operator ?\n\n    Contract03 as abstract\n      action03() as abstract\n        <- rtn as String?\n      default operator ?\n    Impl03 extends Contract03\n      override action03()\n        <- rtn <- \"c03\"\n      default operator ?\n\n    Contract04 as abstract\n      action04() as abstract\n        <- rtn as String?\n      default operator ?\n    Impl04 extends Contract04\n      override action04()\n        <- rtn <- \"c04\"\n      default operator ?\n\n    Contract05 as abstract\n      action05() as abstract\n        <- rtn as String?\n      default operator ?\n    Impl05 extends Contract05\n      override action05()\n        <- rtn <- \"c05\"\n      default operator ?\n\n    Contract06 as abstract\n      action06() as abstract\n        <- rtn as String?\n      default operator ?\n    Impl06 extends Contract06\n      override action06()\n        <- rtn <- \"c06\"\n      default operator ?\n\n    Contract07 as abstract\n      action07() as abstract\n        <- rtn as String?\n      default operator ?\n    Impl07 extends Contract07\n      override action07()\n        <- rtn <- \"c07\"\n      default operator ?\n\n    Contract08 as abstract\n      action08() as abstract\n        <- rtn as String?\n      default operator ?\n    Impl08 extends Contract08\n      override action08()\n        <- rtn <- \"c08\"\n      default operator ?\n\n    Contract09 as abstract\n      action09() as abstract\n        <- rtn as String?\n      default operator ?\n    Impl09 extends Contract09\n      override action09()\n        <- rtn <- \"c09\"\n      default operator ?\n\n    Contract10 as abstract\n      action10() as abstract\n        <- rtn as String?\n      default operator ?\n    Impl10 extends Contract10\n      override action10()\n        <- rtn <- \"c10\"\n      default operator ?\n\n    Contract11 as abstract\n      action11() as abstract\n        <- rtn as String?\n      default operator ?\n    Impl11 extends Contract11\n      override action11()\n        <- rtn <- \"c11\"\n      default operator ?\n\n    Contract12 as abstract\n      action12() as abstract\n        <- rtn as String?\n      default operator ?\n    Impl12 extends Contract12\n      override action12()\n        <- rtn <- \"c12\"\n      default operator ?\n\n    Contract13 as abstract\n      action13() as abstract\n        <- rtn as String?\n      default operator ?\n    Impl13 extends Contract13\n      override action13()\n        <- rtn <- \"c13\"\n      default operator ?\n\n  defines application\n\n    <?-\n      PlatformApp has 11 registrations, under the limit of 12.\n      Adding 2 more would trigger E11042.\n    -?>\n    PlatformApp\n      register Impl01() as Contract01\n      register Impl02() as Contract02\n      register Impl03() as Contract03\n      register Impl04() as Contract04\n      register Impl05() as Contract05\n      register Impl06() as Contract06\n      register Impl07() as Contract07\n      register Impl08() as Contract08\n      register Impl09() as Contract09\n      register Impl10() as Contract10\n      register Impl11() as Contract11\n\n  defines program\n\n    ExcessiveMethodInjectionDemo() with application of PlatformApp\n      stdout <- Stdout()\n      svc as Contract01!\n      result <- svc.action01()\n      stdout.println(`Result: ${result}`)\n      stdout.println(\"PlatformApp has 11 registrations (under limit of 12)\")","migrationContext":"Java Spring: unlimited beans per context. Python: no DI limits. Go Wire: no limits. Rust: no standard DI. EK9: compiler enforces 12-registration limit per application block.","keywords":["DI","E11042","application","composition","limit","modular","monolith","quality","registration","split"],"primaryTopics":["application registration limit","E11042","EXCESSIVE_APPLICATION_REGISTRATIONS"],"typicalErrors":[{"error":"E11042","correct":"      register Impl10() as Contract10\n      register Impl11() as Contract11","incorrect":"      register Impl10() as Contract10\n      register Impl11() as Contract11\n      register Impl12() as Contract12\n      register Impl13() as Contract13","explanation":"Adding 2 more registrations brings the total to 13, exceeding the limit of 12. Split into focused sub-applications. See ek9 -h E11042 for details."}],"companions":[]}
{"id":962,"category":"Code Quality","question":"What is a data clump and how does EK9 detect it?","url":"https://ek9.io/qa/QA0962.html","alternatePhrasings":["What triggers E11053 DATA_CLUMP_DETECTED?","How many functions sharing parameters trigger the data clump error?","How do I extract a record from repeated parameters?"],"answer":"A data clump occurs when 3 or more callables share 4 or more parameters with the same types and names. EK9 detects this at compile time and raises E11053.\n\nWHY THIS MATTERS\nRepeated parameter groups signal a missing abstraction. Martin Fowler identified data clumps as a code smell in 'Refactoring' (1999). The repeated parameters should be extracted into a record.\n\nTHRESHOLD\n- 4 or more parameters with matching types and names\n- Shared across 3 or more callables in the same module\n\nTHE FIX\nExtract a record:\n  defines record\n    Coordinate\n      latitude Float: 0.0\n      longitude Float: 0.0\n      altitude Float: 0.0\n      heading Float: 0.0\nThen pass the record instead of 4 separate parameters.\n\nTHIS EXAMPLE\nTwo functions share 4 Float parameters (latitude, longitude, altitude, heading). With only 2 functions sharing the clump, this is below the threshold of 3.\n\nThe typicalError mutation adds a third function with the same 4 parameters, triggering E11053.\n\nSee Q313 for code smells. See Q310 for quality overview.","ek9Example":"defines module qa.quality.dataclump\n\n  defines function\n\n    <?-\n      Computes a distance metric from navigation coordinates.\n      Uses 4 Float parameters: latitude, longitude, altitude, heading.\n    -?>\n    computeDistance()\n      ->\n        latitude as Float\n        longitude as Float\n        altitude as Float\n        heading as Float\n      <- result as Float: latitude + longitude + altitude + heading\n\n    <?-\n      Calculates a heading adjustment from the same navigation coordinates.\n      Same 4 parameters as computeDistance, but only 2 functions share them.\n      Below the threshold of 3 callables.\n    -?>\n    calculateHeading()\n      ->\n        latitude as Float\n        longitude as Float\n        altitude as Float\n        heading as Float\n      <- result as Float: heading + latitude + longitude + altitude\n\n  defines program\n\n    DataClumpDemo()\n      stdout <- Stdout()\n      distance <- computeDistance(latitude: 51.5, longitude: 0.12, altitude: 100.0, heading: 270.0)\n      bearing <- calculateHeading(latitude: 51.5, longitude: 0.12, altitude: 100.0, heading: 270.0)\n      stdout.println(`Distance: ${distance}`)\n      stdout.println(`Bearing: ${bearing}`)","migrationContext":"Java: data clumps detected only by SonarQube or PMD (optional). Python: no detection. C++: no detection. Go: convention recommends structs but not enforced. EK9: compile-time detection when 3+ callables share 4+ matching parameters.","keywords":["E11053","clump","data","extract","function","parameter","quality","record","refactoring","smell"],"primaryTopics":["data clump","E11053","DATA_CLUMP_DETECTED"],"typicalErrors":[{"error":"E11053","correct":"    calculateHeading()\n      ->\n        latitude as Float\n        longitude as Float\n        altitude as Float\n        heading as Float\n      <- result as Float: heading + latitude + longitude + altitude","incorrect":"    calculateHeading()\n      ->\n        latitude as Float\n        longitude as Float\n        altitude as Float\n        heading as Float\n      <- result as Float: heading + latitude + longitude + altitude\n\n    estimateArrival()\n      ->\n        latitude as Float\n        longitude as Float\n        altitude as Float\n        heading as Float\n      <- estimate as Float: latitude + longitude + altitude + heading","explanation":"Adding a third function with the same 4 Float parameters (latitude, longitude, altitude, heading) triggers E11053. Extract a Coordinate record instead. See ek9 -h E11053 for details."}],"companions":[]}
{"id":963,"category":"Operators and Expressions","question":"How does the <? coalescing minimum operator work in EK9?","url":"https://ek9.io/qa/QA0963.html","alternatePhrasings":["What does <? do in EK9?","How do I find the minimum of two values safely in EK9?","What is EK9's safe minimum operator?","How does coalescing less-than work?"],"answer":"The <? operator is a COALESCING MINIMUM. It returns the smaller of two SET values. If either value is UNSET, the result depends on which is set:\n\n- Both SET: returns the smaller value (like Math.min)\n- Left SET, right UNSET: returns left (the SET one)\n- Left UNSET, right SET: returns right (the SET one)\n- Both UNSET: returns UNSET\n\nThis is unique to EK9 — no other language has coalescing comparators.\n\nThe >? operator is the coalescing MAXIMUM (same rules but returns the larger).\n\nBridge: There is no equivalent in Java, Python, Kotlin, or Rust. The closest analogy is a null-safe Math.min() that automatically handles missing values.\n\nSee Q899 for the full coalescing operator family. See Q24 for tri-state semantics.","ek9Example":"defines module qa.operators.coalescingmin\n\n  defines function\n\n    <?-\n      Find the cheaper price using <? coalescing minimum.\n      If either price is unset, returns the other.\n    -?>\n    bestDeal() as pure\n      ->\n        onlinePrice as Float\n        storePrice as Float\n      <-\n        rtn as Float: onlinePrice <? storePrice\n\n    <?-\n      Find the closest deadline using <? coalescing minimum on dates.\n      Works with any type that has the <=> comparison operator.\n    -?>\n    nearestDeadline() as pure\n      ->\n        deadlineA as Date\n        deadlineB as Date\n      <-\n        rtn as Date: deadlineA <? deadlineB\n\n    maxOf() as pure\n      ->\n        left as Integer\n        right as Integer\n      <- rtn as Integer: left >? right\n\n    chainedMinFloat() as pure\n      ->\n        a as Float\n        b as Float\n        c as Float\n      <- rtn as Float: a <? b <? c\n\n  defines program\n\n    CoalescingMinDemo()\n      stdout <- Stdout()\n\n      // Both values set — returns the smaller\n      cheapest <- bestDeal(29.99, 19.99)\n      stdout.println(`Cheapest: ${cheapest}`)\n\n      // One value unset — returns the SET one\n      knownPrice <- 25.0\n      unknownPrice <- Float()\n      safePrice <- knownPrice <? unknownPrice\n      stdout.println(`Safe price: ${safePrice}`)\n\n      // Both unset — result is unset\n      noPrice1 <- Float()\n      noPrice2 <- Float()\n      noResult <- noPrice1 <? noPrice2\n      if noResult?\n        stdout.println(\"Should not reach here\")\n      else\n        stdout.println(\"Both unset, result is unset\")\n\n      // >? coalescing maximum — returns the larger\n      topScore <- maxOf(85, 92)\n      stdout.println(`Top score: ${topScore}`)\n\n      // Chaining: find minimum of three values\n      lowest <- chainedMinFloat(30.0, 15.0, 22.0)\n      stdout.println(`Lowest of three: ${lowest}`)","migrationContext":"Java: Math.min(a, b) requires null checks. Python: min(a, b) throws on None. Kotlin: minOf(a, b) requires ?: null handling. EK9's <? combines comparison + unset handling in one operator.","keywords":["coalescing","less than","minimum","safe comparison","unset handling"],"primaryTopics":["<? operator","coalescing minimum"],"typicalErrors":[{"error":"E50001","correct":"onlinePrice <? storePrice","incorrect":"min(onlinePrice, storePrice)","explanation":"EK9 has no min() function so the call cannot be resolved — use the <? coalescing minimum operator instead. See ek9 -h E50001 for details."}],"companions":[]}
{"id":964,"category":"Operators and Expressions","question":"How does the >? coalescing maximum operator work in EK9?","url":"https://ek9.io/qa/QA0964.html","alternatePhrasings":["What does >? do in EK9?","How do I find the maximum of two values safely in EK9?","What is EK9's safe maximum operator?","How does coalescing greater-than work?"],"answer":"The >? operator is a COALESCING MAXIMUM. It returns the larger of two SET values. If either value is UNSET, the result depends on which is set:\n\n- Both SET: returns the larger value (like Math.max)\n- Left SET, right UNSET: returns left (the SET one)\n- Left UNSET, right SET: returns right (the SET one)\n- Both UNSET: returns UNSET\n\nThis is unique to EK9 — no other language has coalescing comparators.\n\nThe <=? and >=? operators also exist for 'less-or-equal minimum' and 'greater-or-equal maximum'.\n\nSee Q899 for the full coalescing operator family. See Q963 for <? coalescing minimum.","ek9Example":"defines module qa.operators.coalescingmax\n\n  defines function\n\n    <?-\n      Find the highest temperature using >? coalescing maximum.\n      If either reading is unset, returns the other.\n    -?>\n    peakTemperature() as pure\n      ->\n        morningReading as Float\n        afternoonReading as Float\n      <-\n        rtn as Float: morningReading >? afternoonReading\n\n    maxOf() as pure\n      ->\n        left as Integer\n        right as Integer\n      <- rtn as Integer: left >? right\n\n    minOrEqual() as pure\n      ->\n        left as Integer\n        right as Integer\n      <- rtn as Integer: left <=? right\n\n    maxOrEqual() as pure\n      ->\n        left as Integer\n        right as Integer\n      <- rtn as Integer: left >=? right\n\n  defines program\n\n    CoalescingMaxDemo()\n      stdout <- Stdout()\n\n      // Both values set — returns the larger\n      highest <- maxOf(85, 92)\n      stdout.println(`Highest: ${highest}`)\n\n      // One value unset — returns the SET one\n      knownScore <- 75\n      missingScore <- Integer()\n      safeScore <- knownScore >? missingScore\n      stdout.println(`Safe score: ${safeScore}`)\n\n      // Both unset — result is unset\n      noScore1 <- Integer()\n      noScore2 <- Integer()\n      noResult <- noScore1 >? noScore2\n      if noResult?\n        stdout.println(\"Should not reach here\")\n      else\n        stdout.println(\"Both unset, result is unset\")\n\n      // <=? coalescing less-or-equal minimum\n      minimum <- minOrEqual(10, 10)\n      stdout.println(`<=? with equal: ${minimum}`)\n\n      // >=? coalescing greater-or-equal maximum\n      maximum <- maxOrEqual(10, 10)\n      stdout.println(`>=? with equal: ${maximum}`)\n\n      // Practical use: find peak temperature across readings\n      peak <- peakTemperature(22.5, 31.8)\n      stdout.println(`Peak temp: ${peak}`)","migrationContext":"Java: Math.max(a, b) requires null checks. Python: max(a, b) throws on None. EK9's >? combines comparison + unset handling in one operator. No equivalent exists in any mainstream language.","keywords":["coalescing","greater than","maximum","safe comparison","unset handling"],"primaryTopics":[">? operator","coalescing maximum"],"typicalErrors":[{"error":"E50001","correct":"highest <- maxOf(85, 92)","incorrect":"highest <- max(85, 92)","explanation":"EK9 has no max() function so 'max' will not resolve (E50001); use the >? coalescing maximum operator instead. See ek9 -h E50001 for details."}],"companions":[]}
{"id":965,"category":"Getting Started","question":"What are the three assignment operators in EK9 and when do I use each?","url":"https://ek9.io/qa/QA0965.html","alternatePhrasings":["What is the difference between <- and := in EK9?","When do I use <- vs := vs :=? in EK9?","How do declaration and assignment differ in EK9?","What does <- mean in EK9?","What does := mean in EK9?","What does :=? mean in EK9?"],"answer":"EK9 has assignment operators for declaration and reassignment. EK9 separates declaration from reassignment with distinct operators:\n\n<- DECLARATION WITH TYPE INFERENCE: Creates a NEW variable, compiler infers the type.\n  name <- \"Steve\"     creates variable 'name' (inferred as String)\n  count <- 0           creates variable 'count' (inferred as Integer)\n  stdout <- Stdout()   creates variable 'stdout' (inferred as Stdout)\n\n:= or : or = ASSIGNMENT: All three are interchangeable for reassignment.\n  name := \"Alice\"     changes existing 'name' to \"Alice\"\n  name: \"Alice\"       same effect — : is shorthand for :=\n  name = \"Alice\"      same effect — = also works\n\nWith EXPLICIT TYPE declaration, use : or := or = with a type:\n  age as Integer: 30     declares 'age' as Integer with value 30\n  age as Integer := 30   same effect\n  age as Integer = 30    same effect\n\n:=? GUARDED ASSIGNMENT: Only assigns if the target is currently UNSET.\n  name :=? \"Default\"   assigns \"Default\" ONLY if name is unset\n  name :=? \"Other\"     does nothing — name is already set\n\nKEY RULES:\n- <- is declaration with type inference (used once per variable)\n- :=, :, = are interchangeable for reassignment and explicit type declaration\n- :=? is safe for conditional initialization\n\nEK9 makes this distinction at the operator level — <- always means 'new variable', := always means 'update existing'.","ek9Example":"defines module qa.gettingstarted.assignmentops\n\n  defines program\n\n    AssignmentOpsDemo()\n      stdout <- Stdout()\n\n      // <- DECLARATION: creates new variable\n      greeting <- \"Hello\"\n      stdout.println(`After <-: ${greeting}`)\n\n      // := ASSIGNMENT: updates existing variable\n      greeting := \"Hi there\"\n      stdout.println(`After :=: ${greeting}`)\n\n      // := again on same variable — fine, it exists\n      greeting := \"Hey\"\n      stdout.println(`After := again: ${greeting}`)\n\n      // :=? GUARDED: only assigns if unset\n      nickname <- String()\n      stdout.println(`Before :=?: nickname is ${nickname?}`)\n      nickname :=? \"Buddy\"\n      stdout.println(`After :=? on unset: ${nickname}`)\n      nickname :=? \"Pal\"\n      stdout.println(`After :=? on set: ${nickname}`)\n\n      // Practical example: building a configuration\n      host <- \"localhost\"\n      port <- 8080\n      host := \"production.example.com\"\n      port := 443\n      stdout.println(`Config: ${host}:${port}`)","migrationContext":"Go separates declaration (:=) from assignment (=), EK9 does the same with <- and :=. Java and Python use = for both (ambiguous). Kotlin uses val/var keywords, EK9 uses operators.","keywords":["assignment","declaration","guarded","reassignment","variable creation"],"primaryTopics":["<- operator",":= operator",":=? operator","assignment operators"],"typicalErrors":[],"companions":[]}
{"id":966,"category":"Operators and Expressions","question":"What does the #? hashcode operator do in EK9?","url":"https://ek9.io/qa/QA0966.html","alternatePhrasings":["How do I get a hashcode in EK9?","What is #? in EK9?","How does hashcode work in EK9?","What does hash question mark mean in EK9?"],"answer":"The #? operator is the HASHCODE operator in EK9. It calls the _hashcode() method on the object and returns an Integer.\n\nSyntax: #? variableName (prefix operator)\n\nIMPORTANT: #? is NOT a Python comment. It is NOT a Ruby method call. The # prefix in EK9 means 'introspection' — #? is hashcode, #^ is promote, #< is first, #> is last.\n\nEK9 operator method names:\n  #? calls _hashcode() — returns Integer\n  #^ calls _promote() — returns a different type (type widening)\n  $ calls _string() — returns String (string CONVERSION)\n  ? calls _isSet() — returns Boolean\n\nThe 'default operator' keyword auto-generates #? from declared fields. You rarely need to implement it manually.\n\nBridge: Like Java's hashCode() but as a prefix operator. Like Python's hash() but with # prefix syntax.","ek9Example":"defines module qa.operators.hashcodedetail\n\n  defines record\n\n    <?-\n      Simple record with auto-generated #? operator via 'default operator'.\n    -?>\n    Coordinate\n      latitude as Float: Float()\n      longitude as Float: Float()\n\n      Coordinate()\n        ->\n          latitude as Float\n          longitude as Float\n        this.latitude :=: latitude\n        this.longitude :=: longitude\n\n      default operator\n\n  defines program\n\n    HashcodeDemo()\n      stdout <- Stdout()\n\n      // #? on built-in types\n      greeting <- \"Hello\"\n      nameHash <- #? greeting\n      stdout.println(`String hash: ${nameHash}`)\n\n      age <- 42\n      ageHash <- #? age\n      stdout.println(`Integer hash: ${ageHash}`)\n\n      // #? on custom record (auto-generated by 'default operator')\n      location <- Coordinate(51.5074, -0.1278)\n      locationHash <- #? location\n      stdout.println(`Coordinate hash: ${locationHash}`)\n\n      // Comparing hashcodes\n      sameLocation <- Coordinate(51.5074, -0.1278)\n      if #? location == #? sameLocation\n        stdout.println(\"Same hashcodes for equal coordinates\")","migrationContext":"Java: obj.hashCode() — EK9: #? obj. Python: hash(obj) — EK9: #? obj. Kotlin: obj.hashCode() — EK9: #? obj. The # prefix is EK9's introspection family, NOT a comment character.","keywords":["hash","hashcode","introspection","prefix operator"],"primaryTopics":["#? operator","hashcode","_hashcode method"],"typicalErrors":[{"error":"E50060","correct":"      nameHash <- #? greeting","incorrect":"      nameHash <- greeting.hashCode()","explanation":"EK9 has no Java-style '.hashCode()' method; use the '#?' prefix operator, as 'hashCode' resolves on no EK9 type. See ek9 -h E50060 for details."}],"companions":[]}
{"id":967,"category":"Operators and Expressions","question":"Does EK9 have ++ and -- operators? What are the rules?","url":"https://ek9.io/qa/QA0967.html","alternatePhrasings":["Can I use ++ in EK9?","How does increment work in EK9?","Are ++ and -- expressions or statements in EK9?","Why can't I write y <- x++ in EK9?"],"answer":"YES, EK9 has ++ and -- operators. But they are STATEMENT-ONLY — they cannot be used in expressions.\n\nVALID (statement-only):\n  count++         increments count by 1\n  count--         decrements count by 1\n  temperature++   increments temperature\n\nINVALID (not an expression):\n  y <- x++        COMPILE ERROR — ++ cannot be used in an expression\n  total <- count++ + 5    COMPILE ERROR\n  if x++ > 10     COMPILE ERROR\n\nWHY STATEMENT-ONLY?\nMutating operators (++, --, +=, -=) return 'this' — the SAME object, not a copy. This means y <- x++ would make y and x point to the SAME object (aliasing). This is EK9's most dangerous gotcha and is prevented by the compiler.\n\nBridge: Like C++/Java's ++ but restricted to statements. Similar to how Python chose not to have ++ at all — EK9 allows it but prevents the aliasing danger.\n\nOTHER MUTATING OPERATORS:\n  x += 5    statement-only, adds 5 to x\n  x -= 3    statement-only, subtracts 3 from x\n  x *= 2    statement-only, multiplies x by 2\n\nAll mutating operators follow the same rule: statement-only, no expression use.\n\nSee Q905 for mutating operator aliasing details. See Q241 for the full mutation operator list.","ek9Example":"defines module qa.operators.incrementrules\n\n  defines program\n\n    IncrementDemo()\n      stdout <- Stdout()\n\n      // ++ as statement — VALID\n      counter <- 0\n      counter++\n      stdout.println(`After ++: ${counter}`)\n\n      counter++\n      counter++\n      stdout.println(`After two more ++: ${counter}`)\n\n      // -- as statement — VALID\n      counter--\n      stdout.println(`After --: ${counter}`)\n\n      // += as statement — VALID\n      counter += 10\n      stdout.println(`After += 10: ${counter}`)\n\n      // -= as statement — VALID\n      counter -= 3\n      stdout.println(`After -= 3: ${counter}`)\n\n      // The correct pattern when you need the value:\n      // Increment first, then use the variable\n      score <- 100\n      score++\n      displayScore <- score\n      stdout.println(`Score after increment: ${displayScore}`)\n\n      // NOT: displayScore <- score++  (COMPILE ERROR)","migrationContext":"Java: i++ works in expressions (causes bugs). C: i++ is expression (undefined behaviour in some cases). Python: no ++ at all. EK9: ++ exists but is statement-only — the safest of all approaches.","keywords":["aliasing","decrement","increment","mutation","statement only"],"primaryTopics":["++ operator","-- operator","statement-only operators","mutating operators"],"typicalErrors":[{"error":"E07950","correct":"      displayScore <- score","incorrect":"      displayScore <- score++","explanation":"The '++' operator is statement-only in EK9 — it cannot be used inside an expression like 'displayScore <- score++'; increment on its own line first, then use the variable. See ek9 -h E07950 for details."}],"companions":[]}
{"id":968,"category":"Getting Started","question":"Why does EK9 separate variable declaration from assignment?","url":"https://ek9.io/qa/QA0968.html","alternatePhrasings":["Why can't I just use = for everything in EK9?","What's the point of having <- and := as separate operators?","Why does EK9 force me to use <- the first time?","How is <- different from := in practice?"],"answer":"EK9 separates declaration (<-) from assignment (:=) to prevent accidental variable creation and make variable lifetimes explicit.\n\nTHE PROBLEM IN OTHER LANGUAGES:\nIn Python/JavaScript, x = 5 either creates or reassigns — you can't tell which. This leads to:\n- Accidental shadowing (creating a new variable when you meant to update)\n- Typo bugs (misspelling a variable name creates a new one silently)\n- Unclear lifetime (when was this variable created?)\n\nEK9'S SOLUTION:\n  <- means 'I am creating something new'\n  := means 'I am updating something that exists'\n\nThis makes every variable's birth point explicit in the code.\n\nMany languages (Go, Rust, Ada) also separate declaration from assignment. EK9 uses <- for new variables and := for updates — making every variable's birth point visible in the code.\n\nSee Q965 for the full assignment operator guide.","ek9Example":"defines module qa.gettingstarted.declvsassign\n\n  defines function\n\n    <?-\n      Shows the lifecycle: declare with <-, update with :=.\n      EK9 separates declaration (<-) from assignment (:=).\n    -?>\n    buildGreeting()\n      ->\n        firstName as String\n        lastName as String\n      <-\n        rtn as String: String()\n\n      // <- creates the variable (declaration)\n      fullName <- `${firstName} ${lastName}`\n\n      // := updates the variable (assignment to existing variable)\n      fullName := `${fullName}!`\n\n      rtn := fullName\n\n  defines program\n\n    DeclVsAssignDemo()\n      stdout <- Stdout()\n\n      // <- declares new variables\n      message <- buildGreeting(\"Steve\", \"Limb\")\n      stdout.println(message)\n\n      // := updates existing variable\n      message := buildGreeting(\"Alice\", \"Smith\")\n      stdout.println(message)\n\n      // :=? only assigns if currently unset\n      optionalName <- String()\n      optionalName :=? \"Fallback\"\n      stdout.println(`Optional: ${optionalName}`)\n\n      // :=? has no effect when already set\n      optionalName :=? \"Ignored\"\n      stdout.println(`Still: ${optionalName}`)","migrationContext":"Go separates declaration from reassignment with different operators, just like EK9. Rust uses 'let' keyword for declaration. Python and Java use = for everything. EK9 makes the distinction at the operator level: <- for new, := for update.","keywords":["assignment","declaration","lifetime","shadowing","variable creation"],"primaryTopics":["<- vs :=","variable declaration","assignment separation"],"typicalErrors":[{"error":"E50001","correct":"      message <- buildGreeting(\"Steve\", \"Limb\")","incorrect":"      message := buildGreeting(\"Steve\", \"Limb\")","explanation":"The first use of a variable must declare it with '<-'; using ':=' on 'message' before it exists means the name does not resolve. See ek9 -h E50001 for details."}],"companions":[]}
{"id":969,"category":"Operators and Expressions","question":"What does the #^ promote operator do in EK9?","url":"https://ek9.io/qa/QA0969.html","alternatePhrasings":["How do I convert types in EK9?","What is #^ in EK9?","How does type promotion work in EK9?","What does hash caret mean in EK9?"],"answer":"The #^ operator is the PROMOTE operator in EK9. It calls the _promote() method on the object and returns a DIFFERENT type (type widening/conversion).\n\nSyntax: #^ variableName (prefix operator)\n\nIMPORTANT: #^ is NOT a Python comprehension. It is NOT a Ruby hash. The # prefix in EK9 means 'introspection' — #^ is promote, #? is hashcode, #< is first, #> is last.\n\nEK9 introspection operators:\n  #^ calls _promote() — type CONVERSION to a wider type\n  #? calls _hashcode() — returns Integer hashcode\n  #< calls _first() — returns first value (enumerations)\n  #> calls _last() — returns last value (enumerations)\n\nTypical use: converting a Character to a String, or a narrower numeric type to a wider one.\n\nThe 'default operator' keyword auto-generates #^ from declared fields. You rarely implement it manually.\n\nBridge: Like Java's widening conversion (int to long) but as an explicit operator. Like Rust's From/Into traits but with operator syntax.","ek9Example":"defines module qa.operators.promotedetail\n\n  defines program\n\n    PromoteDemo()\n      stdout <- Stdout()\n\n      // #^ on Character — promotes to String\n      letter <- 'A'\n      letterAsString <- #^ letter\n      stdout.println(`Character promoted to String: ${letterAsString}`)\n\n      // #^ on Integer — may promote to Float depending on type\n      wholeNumber <- 42\n      promoted <- #^ wholeNumber\n      stdout.println(`Integer promoted: ${promoted}`)\n\n      // The promote operator returns a DIFFERENT type\n      // This is what distinguishes it from $ (string conversion)\n      // $ always returns String, #^ returns whatever _promote() is defined to return","migrationContext":"Java: implicit widening (int -> long) or explicit casting. Python: int(x), str(x) conversion functions. EK9: #^ prefix operator calls _promote(). The # prefix is EK9's introspection family — NOT a comment.","keywords":["introspection","prefix operator","promote","type conversion","widening"],"primaryTopics":["#^ operator","promote","_promote method","type conversion"],"typicalErrors":[],"companions":[]}
{"id":970,"category":"Streams and Pipelines","question":"How do I output stream results to stdout or a collection in EK9?","url":"https://ek9.io/qa/QA0970.html","alternatePhrasings":["How do stream terminals work in EK9?","What does > mean in EK9 streams?","How do I redirect stream output in EK9?","How do I collect stream results in EK9?"],"answer":"EK9 stream pipelines use Unix-style > for terminal output. The > operator sends stream results to a sink (like Stdout, a collection, or a file).\n\nTERMINAL OPERATORS:\n  > stdout          sends each item to stdout (like Unix > redirect)\n  > myList          sends items into a collection\n  >> myList         appends items to existing collection\n  collect as Type   materializes stream into new collection\n\nIMPORTANT: Use > like Unix redirect — NOT stdout <- cat items.\n\nCORRECT:\n  cat items | filter by isValid > stdout\n  cat numbers | sort by ascending | head 5 > stdout\n  cat names | map with toUpperCase | collect as List of String\n\nWRONG:\n  stdout <- cat items | filter by isValid   WRONG — do not use <- for stream output\n  result = cat items | collect               WRONG — use 'collect as Type'\n\nBridge: Like Unix pipes: ls | grep foo | head 5 > output.txt. The > in EK9 streams works exactly like > in shell — it redirects the pipeline output to a destination.\n\nSee Q235 for full stream operations reference. See Q237 for streams vs loops comparison.","ek9Example":"defines module qa.streams.outputpatterns\n\n  defines function\n\n    <?-\n      Check if a number is even.\n    -?>\n    isEven() as pure\n      -> numberToCheck as Integer\n      <- rtn as Boolean: numberToCheck mod 2 == 0\n\n  defines program\n\n    StreamOutputDemo()\n      stdout <- Stdout()\n\n      // > stdout — sends each item to standard output\n      stdout.println(\"Even numbers:\")\n      cat [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]\n        | filter by isEven\n        > stdout\n\n      // collect as — materializes into a new collection\n      stdout.println(\"Collected evens:\")\n      evens <- cat [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]\n        | filter by isEven\n        | collect as List of Integer\n      stdout.println($evens)\n\n      // head N — take first N then > to sink\n      stdout.println(\"First 3:\")\n      cat [10, 20, 30, 40, 50]\n        | head 3\n        > stdout","migrationContext":"Unix shell: command | filter | head > output — same concept in EK9. Java Streams: .collect(Collectors.toList()) — EK9: collect as List of T. Python: list comprehension — EK9: cat source | filter | collect.","keywords":["collect","redirect","sink","stdout","stream output","terminal"],"primaryTopics":["stream terminals","> operator in streams","collect as","stream output"],"typicalErrors":[],"companions":[]}
{"id":971,"category":"Control Flow","question":"How do guard expressions work in EK9 if statements?","url":"https://ek9.io/qa/QA0971.html","alternatePhrasings":["What is a guard expression in EK9?","How does if v <- expr() work in EK9?","How do I combine declaration with null checking in EK9?","What does the <- guard do inside an if statement?"],"answer":"Guard expressions combine variable handling with isSet checking in a single statement. The block only executes if the value is SET.\n\nTHREE GUARD FORMS:\n\n1. DECLARATION GUARD (<-):\n  if name <- getName()\n    stdout.println(name)   declares name, only runs if SET\n\n2. ASSIGNMENT GUARD (:=):\n  selectedItem <- String()\n  if selectedItem := findItem(\"key\")\n    stdout.println(selectedItem)   assigns to existing var, only runs if SET\n\n3. GUARDED ASSIGNMENT (:=?):\n  config <- loadDefaults()\n  if config :=? loadOverrides()\n    stdout.println(config)   only assigns if loadOverrides() returned SET\n\nThe := guard always assigns, then checks isSet. The :=? guard only assigns when the right side is SET — the existing value is preserved if the right side is unset.\n\nWITH ADDITIONAL CONDITION:\n  if name <- getName() then name.length() > 3\n    stdout.println(name)   runs if SET AND length > 3\n\nAll three guard forms work IDENTICALLY across all control flow:\n  if v <- expr()         declaration guard\n  switch v := expr()     assignment guard\n  while v :=? expr()     guarded assignment\n  try v <- expr()        declaration guard in try\n  for v <- iterator      declaration guard in for\n\nThis eliminates 90-95% of null pointer exceptions through compile-time enforcement.\n\nBridge: Like Go's 'if err := doSomething(); err != nil'. Like Rust's 'if let Some(v) = expr()'. Like Swift's 'if let v = optionalExpr'.\n\nSee Q74 for more guard examples. See Q75 for guard switch.","ek9Example":"defines module qa.controlflow.guardif\n\n  defines function\n\n    <?-\n      Returns a greeting if the name has content, or unset if empty.\n    -?>\n    findGreeting() as pure\n      -> personName as String\n      <- rtn as String: String()\n\n      if personName?\n        rtn: \"Hello, \" + personName\n\n  defines program\n\n    GuardIfDemo()\n      stdout <- Stdout()\n\n      // BASIC GUARD: only enters if findGreeting returns SET\n      if greeting <- findGreeting(\"Steve\")\n        stdout.println(greeting)\n\n      // GUARD WITH UNSET: block is skipped\n      if greeting <- findGreeting(String())\n        stdout.println(\"This won't print\")\n      else\n        stdout.println(\"Greeting was unset - skipped\")\n\n      // GUARD WITH CONDITION: SET and passes additional test\n      minLength <- 3\n      if greeting <- findGreeting(\"Alice\") then greeting.length() > minLength\n        stdout.println(`Long greeting: ${greeting}`)\n\n      // GUARD IN SWITCH: declares and checks in one step\n      switch greet <- findGreeting(\"Charlie\") with greet\n        case == \"Hello, Charlie\"\n          stdout.println(\"Found Charlie's greeting\")\n        default\n          stdout.println(`Other greeting: ${greet}`)","migrationContext":"Go: if err := f(); err != nil { } — same concept. Rust: if let Some(v) = expr { } — same concept. Swift: if let v = optional { } — same concept. Java: requires separate null check. EK9 guards are universal across all control flow.","keywords":["control flow","declaration guard","guard","isSet check","null safety"],"primaryTopics":["guard expressions","if guard","declaration guard","isSet checking"],"typicalErrors":[{"error":"E01073","correct":"greeting.length() > minLength","incorrect":"greeting.length() > minLength and greeting <> null","explanation":"'null' does not exist in EK9 — use tri-state semantics (unset/set) with guard expressions such as 'if name <- expr()' instead of comparing against null. See ek9 -h E01073 for details."}],"companions":[]}
{"id":972,"category":"Getting Started","question":"My variable already exists but <- creates a new one — help!","url":"https://ek9.io/qa/QA0972.html","alternatePhrasings":["When do I use <- vs := in EK9?","I keep mixing up <- and := in EK9","What is the rule for <- vs := in EK9?"],"answer":"Simple rule: '<-' is for the FIRST TIME. ':=' is for EVERY TIME AFTER.\n\nPATTERN:\n  name <- \"Steve\"     FIRST TIME: <- creates name\n  name := \"Alice\"     AFTER: := updates name\n  name := \"Bob\"       AFTER: := updates name again\n\nThe compiler ENFORCES this:\n- Using := before <- is an error (variable does not exist yet)\n- Using <- twice on the same name is an error (already declared)\n\nAlso: ':=' and ':' and '=' are all interchangeable for reassignment.\n  name := \"Alice\"   same as\n  name: \"Alice\"     same as\n  name = \"Alice\"","ek9Example":"defines module qa.gettingstarted.declarethenassign\n\n  defines program\n\n    DeclareThenAssignDemo()\n      stdout <- Stdout()\n\n      // <- creates the variable (FIRST TIME)\n      greeting <- \"Hello\"\n      stdout.println(greeting)\n\n      // := updates it (EVERY TIME AFTER)\n      greeting := \"Hi there\"\n      stdout.println(greeting)\n\n      // := again — still updating\n      greeting := \"Hey\"\n      stdout.println(greeting)\n\n      // : is shorthand for :=\n      greeting: \"Yo\"\n      stdout.println(greeting)\n\n      // Another variable: <- first, then :=\n      counter <- 0\n      counter := counter + 1\n      counter := counter + 1\n      stdout.println(`Counter: ${counter}`)","migrationContext":"Unlike Java and Python where = does both declaration and reassignment, EK9 separates them: <- declares a new variable, := updates an existing one. This prevents accidental shadowing.","keywords":["arrow","assign","create","declare","first time","update"],"primaryTopics":["<- vs :=","declaration vs assignment"],"typicalErrors":[{"error":"E50001","correct":"greeting <- \"Hello\"","incorrect":"greeting := \"Hello\"","explanation":"The first use of a variable must be '<-' to create it; using ':=' on a name that does not exist yet is not resolved. See ek9 -h E50001 for details."}],"companions":[]}
{"id":973,"category":"Control Flow","question":"How do I declare a variable inside an if condition in EK9?","url":"https://ek9.io/qa/QA0973.html","alternatePhrasings":["What is the if v <- expr pattern in EK9?","How do guards combine declaration and null checking in EK9?","How do I skip a block when a value is unset in EK9?"],"answer":"Use the guard pattern: 'if v <- expression'. This declares v AND checks if it is SET. The block only runs if the value is set.\n\nSYNTAX:\n  if variableName <- someExpression()\n    // only runs if someExpression() returned a SET value\n    // variableName is available here\n\nThe <- inside an if is a GUARD DECLARATION. It combines:\n1. Declare a new variable\n2. Call the expression\n3. Check if the result is SET (not unset/absent)\n4. Only enter the block if SET\n\nWITH ADDITIONAL CONDITION:\n  if v <- getValue() then v > 0\n    // runs if getValue() is SET AND v > 0\n\nWITH ELSE:\n  if result <- compute()\n    stdout.println(result)\n  else\n    stdout.println(\"compute returned unset\")\n\nThis replaces null checks in other languages:\n  Java:   if (x != null) { use(x); }\n  Kotlin: x?.let { use(it) }\n  Rust:   if let Some(x) = expr { use(x); }\n  EK9:    if x <- expr()\n            use(x)\n\nSee Q74 for more guard patterns. See Q971 for guard details.","ek9Example":"defines module qa.controlflow.guarddeclaration\n\n  defines function\n\n    <?-\n      May return a set or unset String.\n    -?>\n    lookupUser() as pure\n      -> userId as Integer\n      <- rtn as String: String()\n\n      if userId > 0\n        rtn: `User-${userId}`\n\n  defines program\n\n    GuardDeclarationDemo()\n      stdout <- Stdout()\n\n      // Guard: declares 'user' and only enters if SET\n      if user <- lookupUser(1)\n        stdout.println(`Found: ${user}`)\n\n      // Guard with unset result: else branch runs\n      if user <- lookupUser(0)\n        stdout.println(\"Should not print\")\n      else\n        stdout.println(\"User not found - guard skipped block\")\n\n      // Guard with additional condition\n      minId <- 1\n      if user <- lookupUser(5) then user.length() > minId\n        stdout.println(`Valid user: ${user}`)","migrationContext":"Go: if err := f(); err != nil. Rust: if let Some(v) = expr. Swift: if let v = optional. Kotlin: val v = expr; if (v != null). EK9: if v <- expr() — one line, compile-time enforced.","keywords":["control flow","guard","if declaration","isSet check","null safety"],"primaryTopics":["guard declaration","if v <- expr"],"typicalErrors":[{"error":"E01073","correct":"      <- rtn as String: String()","incorrect":"      <- rtn as String: null","explanation":"'null' does not exist in EK9 — return the tri-state unset value String() instead of a null literal. See ek9 -h E01073 for details."}],"companions":[]}
{"id":974,"category":"Getting Started","question":"Show me a practical EK9 example using both <- and := together.","url":"https://ek9.io/qa/QA0974.html","alternatePhrasings":["How do <- and := work together in a real EK9 program?","Show me EK9 code where I declare with <- then update with :=","Can you demonstrate the <- then := pattern in EK9?","What does a typical EK9 function look like with <- and :=?"],"answer":"Here is a practical example showing <- for first declaration and := for subsequent updates. The pattern is always: <- once to create, then := (or : or =) to update.\n\nThe function declares 'total' with <- then updates it with := inside the loop. The program declares variables with <- and passes them to the function.","ek9Example":"defines module qa.gettingstarted.arrowequals\n\n  defines function\n\n    <?-\n      Sums a list of integers.\n      Uses <- to declare, := to update.\n    -?>\n    sumList()\n      -> numbers as List of Integer\n      <- rtn as Integer: 0\n\n      for number in numbers\n        rtn := rtn + number\n\n  defines program\n\n    ArrowEqualsDemo()\n      stdout <- Stdout()\n\n      // <- declares new variables\n      scores <- [85, 92, 78, 95, 88]\n      total <- sumList(scores)\n      stdout.println(`Total: ${total}`)\n\n      // := updates existing variable\n      label <- \"Scores\"\n      label := label + \" (5 items)\"\n      stdout.println(label)\n\n      // :=? only assigns if unset\n      fallback <- String()\n      fallback :=? \"No scores available\"\n      stdout.println(`Fallback: ${fallback}`)","migrationContext":"Go: total := 0 then total = total + item. Python: total = 0 then total = total + item (ambiguous). EK9: total <- 0 then total := total + item (explicit separation).","keywords":["assign","declare","loop","practical","update"],"primaryTopics":["<- and := together","declaration then assignment"],"typicalErrors":[{"error":"E50001","correct":"total <- sumList(scores)","incorrect":"total := sumList(scores)","explanation":"The first use of 'total' must declare it with <- ; using := on a variable that does not yet exist leaves it unresolved. See ek9 -h E50001 for details."}],"companions":[]}
{"id":975,"category":"Control Flow","question":"Can guard expressions be used in switch and while, not just if?","url":"https://ek9.io/qa/QA0975.html","alternatePhrasings":["Where can I use the <- guard pattern in EK9?","Does the guard declaration work in switch statements?","Show me guards in while loops in EK9","Is the if v <- expr guard universal across EK9 control flow?"],"answer":"Yes. Guard declarations work IDENTICALLY in if, switch, while, do-while, and try. The same 'v <- expression' pattern means 'declare v, check if SET, only proceed if SET' everywhere.\n\nIN IF:\n  if name <- findUser(id)\n    stdout.println(name)\n\nIN SWITCH:\n  switch record <- database.get(id) with record\n    case .type == \"USER\"\n      processUser(record)\n    default\n      processOther(record)\n\nIN WHILE:\n  while item <- iterator.next()\n    process(item)\n\nIN TRY:\n  try connection <- openDatabase()\n    query(connection)\n  catch\n    -> ex as Exception\n    logError(ex)\n\nThe guard pattern is universal — one syntax to learn, works everywhere. This eliminates 90-95% of null pointer exceptions across ALL control flow.\n\nSee Q971 for if guard details. See Q75 for switch guard details.","ek9Example":"defines module qa.controlflow.guardcontexts\n\n  defines function\n\n    <?-\n      Returns a set or unset value based on input.\n    -?>\n    fetchValue() as pure\n      -> key as String\n      <- rtn as String: String()\n\n      if key?\n        rtn: \"Value for \" + key\n\n  defines program\n\n    GuardContextsDemo()\n      stdout <- Stdout()\n\n      // Guard in IF\n      if greeting <- fetchValue(\"hello\")\n        stdout.println(`IF guard: ${greeting}`)\n\n      // Guard in IF with else\n      if missing <- fetchValue(String())\n        stdout.println(\"Should not print\")\n      else\n        stdout.println(\"IF guard: skipped because unset\")\n\n      // Guard in SWITCH\n      switch found <- fetchValue(\"test\") with found\n        case == \"Value for test\"\n          stdout.println(`SWITCH guard: exact match`)\n        default\n          stdout.println(`SWITCH guard: ${found}`)","migrationContext":"Go: if err := f(); err != nil — only works in if. Rust: if let — only works in if/while. Swift: if let — only in if. EK9: guards work in if, switch, while, do-while, and try — truly universal.","keywords":["guard","switch guard","try guard","universal","while guard"],"primaryTopics":["universal guard pattern","guards in all control flow"],"typicalErrors":[{"error":"E01073","correct":"if key?","incorrect":"if key != null","explanation":"'null' does not exist in EK9; check a value with the '?' isSet operator, not '!= null'. See ek9 -h E01073 for details."}],"companions":[]}
{"id":976,"category":"Classes and OOP","question":"Are record fields public or private in EK9?","url":"https://ek9.io/qa/QA0976.html","alternatePhrasings":["Can I access record fields directly in EK9?","Why do class fields need accessor methods but record fields don't?","How do I access data in a record vs a class in EK9?","What is the visibility difference between records and classes?"],"answer":"Record fields are PUBLIC — accessed directly by name.\nClass fields are PRIVATE — accessed only through methods or operators.\n\nRECORD (public fields):\n  defines record\n    Coordinate\n      latitude as Float: 0.0\n      longitude as Float: 0.0\n\n  // Access directly:\n  point <- Coordinate(51.5, -0.1)\n  stdout.println(`Lat: ${point.latitude}`)    direct field access\n\nCLASS (private fields):\n  defines class\n    Location\n      latitude as Float: 0.0\n\n      latitude() as pure\n        <- rtn as Float: latitude\n\n  // Must use accessor method:\n  loc <- Location(51.5)\n  stdout.println(`Lat: ${loc.latitude()}`)    method call, not field\n\nRecords are for DATA — fields visible, auto-generated operators.\nClasses are for BEHAVIOUR — fields hidden, explicit methods.\n\nRecords can have constructors and operators but NOT methods.\nClasses can have constructors, methods, AND operators.\n\nSee Q900 for full visibility comparison. See Q876 for record basics.","ek9Example":"defines module qa.classesandoop.recordpublicclass\n\n  defines record\n\n    <?-\n      Record fields are PUBLIC — direct access.\n    -?>\n    Point\n      x as Float: 0.0\n      y as Float: 0.0\n\n      Point()\n        ->\n          x as Float\n          y as Float\n        this.x :=: x\n        this.y :=: y\n\n      default operator\n\n  defines class\n\n    <?-\n      Class fields are PRIVATE — accessor methods required.\n    -?>\n    Circle\n      radius as Float: 0.0\n\n      Circle()\n        ->\n          radius as Float\n        this.radius :=: radius\n\n      radius() as pure\n        <- rtn as Float: radius\n\n      default operator\n\n  defines program\n\n    VisibilityDemo()\n      stdout <- Stdout()\n\n      // Record: direct field access (PUBLIC)\n      point <- Point(3.0, 4.0)\n      stdout.println(`Point x: ${point.x}, y: ${point.y}`)\n\n      // Class: must use accessor method (PRIVATE)\n      circle <- Circle(5.0)\n      stdout.println(`Radius: ${circle.radius()}`)","migrationContext":"Java records: fields are private, accessed via generated accessors. Kotlin data classes: properties are public by default. Python dataclasses: attributes are public. EK9 records: fields are PUBLIC (direct access). EK9 classes: fields are PRIVATE (method access only).","keywords":["accessor","class","field","private","public","record","visibility"],"primaryTopics":["record field visibility","class field visibility"],"typicalErrors":[{"error":"E06180","correct":"circle.radius()","incorrect":"circle.radius","explanation":"Class fields are private in EK9, so reading circle.radius directly is not accessible from this context — use the accessor method circle.radius(). See ek9 -h E06180 for details."}],"companions":[]}
{"id":977,"category":"Streams and Pipelines","question":"How do I filter a list and print the results to stdout in EK9?","url":"https://ek9.io/qa/QA0977.html","alternatePhrasings":["Show me a stream pipeline that filters and outputs to stdout","How do I use cat, filter, and > stdout together?","What is the basic stream-to-stdout pattern in EK9?"],"answer":"Use a stream pipeline: cat source | filter by predicate > stdout\n\nThe > operator sends stream results to stdout, like Unix pipe redirect.\n\nPATTERN:\n  cat collection | filter by checkFunction > stdout\n\nThe pipeline reads items from the collection, keeps only those passing the filter, and sends each to stdout.","ek9Example":"defines module qa.streams.filtertostdout\n\n  defines function\n\n    isHighScore() as pure\n      -> score as Integer\n      <- rtn as Boolean?\n\n      threshold <- 80\n      rtn: score > threshold\n\n  defines program\n\n    FilterToStdoutDemo()\n      stdout <- Stdout()\n\n      scores <- [45, 92, 78, 95, 63, 88, 71, 99]\n\n      stdout.println(\"High scores:\")\n      cat scores | filter by isHighScore > stdout","migrationContext":"Java Streams: list.stream().filter(x -> x > 5).forEach(System.out::println). EK9: cat scores | filter by isHigh > stdout — shorter, no lambda syntax.","keywords":["cat","filter","pipeline","stdout","stream"],"primaryTopics":["stream filter to stdout","cat filter redirect"],"typicalErrors":[{"error":"E50001","correct":"      cat scores | filter by isHighScore > stdout","incorrect":"      scores.stream().filter(isHighScore).forEach(stdout::println)","explanation":"EK9 streams use Unix pipe syntax: 'cat source | filter by fn > stdout'. There is no .stream() method or Java-style method chaining. See ek9 -h stream."}],"companions":[]}
{"id":978,"category":"Streams and Pipelines","question":"How do I transform items and collect the results in EK9?","url":"https://ek9.io/qa/QA0978.html","alternatePhrasings":["Show me cat with map and collect as in EK9","How does stream map with work in EK9?","How do I apply a function to every item in a list using streams?"],"answer":"Use map with to transform each item, then collect as to gather results:\n\n  cat names | map with toUpper | collect as List of String\n\nThe map operation applies a function to each element. The collect terminal materializes the stream into a collection.","ek9Example":"defines module qa.streams.mapcollect\n\n  defines function\n\n    formatTemperature() as pure\n      -> celsius as Float\n      <- rtn as String: `${celsius}C`\n\n  defines program\n\n    MapCollectDemo()\n      stdout <- Stdout()\n\n      readings <- [18.5, 22.3, 15.0, 28.7, 20.1]\n\n      labels <- cat readings\n        | map with formatTemperature\n        | collect as List of String\n\n      stdout.println($labels)","migrationContext":"Java: stream.map(String::toUpperCase).collect(Collectors.toList()). EK9: cat names | map with toUpper | collect as List of String.","keywords":["collect","map","pipeline","stream","transform"],"primaryTopics":["stream map collect","stream transformation"],"typicalErrors":[{"error":"E50060","correct":"      labels <- cat readings\n        | map with formatTemperature\n        | collect as List of String","incorrect":"      labels <- readings.stream().map(formatTemperature).collect()","explanation":"EK9 streams use Unix pipe syntax, not Java method chaining. 'stream()' and 'map()' are not methods on List. Write 'cat items | map with fn | collect as Type'."}],"companions":[]}
{"id":979,"category":"Streams and Pipelines","question":"How do I sort a stream and take the top N items in EK9?","url":"https://ek9.io/qa/QA0979.html","alternatePhrasings":["Show me sort by and head in a stream pipeline","How do I get the first 3 items after sorting in EK9?","What replaces break in EK9 stream loops?"],"answer":"Use sort by to order items, then head N to take the first N:\n\n  cat prices | sort by ascending | head 3 > stdout\n\nhead N replaces the break-after-N pattern from other languages. The pipeline stops after emitting N items.\n\nUse > stdout to print results, or collect as to gather into a collection.","ek9Example":"defines module qa.streams.sorthead\n\n  defines function\n\n    compareByAmount() as pure\n      ->\n        left as Float\n        right as Float\n      <- rtn as Integer: left <=> right\n\n  defines program\n\n    SortHeadDemo()\n      stdout <- Stdout()\n\n      expenses <- [45.99, 12.50, 89.00, 23.75, 67.30, 8.99, 155.00]\n\n      stdout.println(\"Top 3 expenses:\")\n      cat expenses\n        | sort by compareByAmount\n        | head 3\n        > stdout","migrationContext":"Java: stream.sorted().limit(3). Python: sorted(items)[:3]. EK9: cat items | sort by comparator | head 3.","keywords":["break replacement","head","pipeline","sort","stream","top N"],"primaryTopics":["stream sort head","head replaces break"],"typicalErrors":[{"error":"E01070","correct":"      cat expenses\n        | sort by compareByAmount\n        | head 3\n        > stdout","incorrect":"      for expense in expenses\n        if count > 3\n          break","explanation":"EK9 has no break statement (E01070). Use 'head N' in a stream pipeline to take the first N items. See ek9 -h E01070."}],"companions":[]}
{"id":980,"category":"Streams and Pipelines","question":"How do reject and tee work in EK9 stream pipelines?","url":"https://ek9.io/qa/QA0980.html","alternatePhrasings":["What is the opposite of filter in an EK9 stream?","How do I copy stream items to a side collection while continuing the pipeline?","Show me tee in an EK9 stream"],"answer":"reject is the inverse of filter — it removes matching items.\ntee copies items to a side collection while continuing the pipeline.\n\n  cat items | reject by isBlank | tee in backup | head 5 > stdout\n\nreject by predicate: removes items where predicate returns true (opposite of filter by).\ntee in collection: copies each item into the collection AND continues the pipeline.","ek9Example":"defines module qa.streams.rejecttee\n\n  defines function\n\n    isBelowThreshold() as pure\n      -> measurement as Float\n      <- rtn as Boolean?\n\n      minimumReading <- 10.0\n      rtn: measurement < minimumReading\n\n  defines program\n\n    RejectTeeDemo()\n      stdout <- Stdout()\n\n      readings <- [5.2, 15.8, 3.1, 22.4, 9.7, 18.3, 7.0]\n\n      validReadings <- List() of Float\n\n      stdout.println(\"Valid readings (above threshold):\")\n      cat readings\n        | reject by isBelowThreshold\n        | tee in validReadings\n        > stdout\n\n      stdout.println(`Saved ${validReadings.length()} valid readings`)","migrationContext":"Java: no reject (use filter with negation). No tee (use peek with side-effect). EK9: reject is first-class, tee is explicit side-copy.","keywords":["pipeline","reject","side copy","stream","tee"],"primaryTopics":["stream reject","stream tee"],"typicalErrors":[{"error":"E01010","correct":"      cat readings\n        | reject by isBelowThreshold\n        | tee in validReadings\n        > stdout","incorrect":"      cat readings\n        | filter by not isBelowThreshold\n        > stdout","explanation":"Use 'reject by' to remove matching items. 'filter by not' is not valid syntax. reject is the inverse of filter."}],"companions":[]}
{"id":981,"category":"Operators and Expressions","question":"How do I check if a variable has a value in EK9?","url":"https://ek9.io/qa/QA0981.html","alternatePhrasings":["What is the set check operator in EK9?","How do I test whether a variable is set before using it?","Show me how to use ? to check if something is set in EK9"],"answer":"Append ? after the variable name. It returns true if the variable holds a value, false if unset or absent.\n\nSYNTAX: variable?\n\n  if userName?\n    stdout.println(userName)\n\n  isReady <- connection? and credentials?\n\n  stdout.println(`Has name: ${userName?}`)\n\nThe ? calls _isSet() internally. No parentheses needed — it is a suffix operator like Kotlin's !! but for checking, not forcing.","ek9Example":"defines module qa.operators.issetconditions\n\n  defines function\n\n    findDiscount() as pure\n      -> loyaltyYears as Integer\n      <- rtn as Float: Float()\n\n      premiumThreshold <- 5\n      standardThreshold <- 2\n\n      if loyaltyYears > premiumThreshold\n        rtn: 0.15\n      else if loyaltyYears > standardThreshold\n        rtn: 0.05\n\n  defines program\n\n    IsSetConditionsDemo()\n      stdout <- Stdout()\n\n      // ? suffix checks if variable is set\n      customerName <- \"Alice\"\n      stdout.println(`Has name: ${customerName?}`)\n\n      // ? in if condition\n      discount <- findDiscount(3)\n      if discount?\n        stdout.println(`Discount: ${discount}`)\n      else\n        stdout.println(\"No discount available\")\n\n      // ? on unset value\n      noDiscount <- findDiscount(1)\n      if noDiscount?\n        stdout.println(\"Should not reach here\")\n      else\n        stdout.println(\"No discount for short tenure\")\n\n      // ? in compound boolean\n      firstName <- \"Bob\"\n      lastName <- String()\n      if firstName? and lastName?\n        stdout.println(\"Both names set\")\n      else\n        stdout.println(\"Not all names set\")","migrationContext":"EK9: append ? after any variable to check if it holds a value. Returns Boolean. No parentheses, no method call — just variable? as a suffix.","keywords":["condition","isSet","set check","suffix","value check"],"primaryTopics":["? set check","isSet in conditions"],"typicalErrors":[{"error":"E01073","correct":"      if discount?\n        stdout.println(`Discount: ${discount}`)","incorrect":"      if discount != null\n        stdout.println(`Discount: ${$discount}`)","explanation":"'null' does not exist in EK9. Use the ? suffix to check if a variable is set: 'if discount?' not 'if discount != null'. See ek9 -h E01073."}],"companions":[]}
{"id":982,"category":"Operators and Expressions","question":"How do ? and guard expressions work together in EK9?","url":"https://ek9.io/qa/QA0982.html","alternatePhrasings":["What is the relationship between ? suffix and if v <- expr guard?","When do I use ? vs a guard in EK9?","Show me ? and guards used together in EK9 code"],"answer":"The ? suffix checks if a value is set. Guards declare AND check in one step.\n\nUSE ? when you already have the variable:\n  if temperature?\n    stdout.println(`Temp: ${$temperature}`)\n\nUSE GUARD when you need to declare and check:\n  if temperature <- readSensor()\n    stdout.println(`Temp: ${$temperature}`)\n\nThe guard 'if v <- expr()' is equivalent to:\n  v <- expr()\n  if v?\n    use v\n\nBut the guard is a single atomic operation — v only exists inside the block.\n\nCOMBINE THEM when you need both:\n  if reading <- readSensor() then reading > minimumThreshold\n    process(reading)","ek9Example":"defines module qa.operators.issetwithguard\n\n  defines function\n\n    lookupPrice() as pure\n      -> productCode as String\n      <- rtn as Float: Float()\n\n      if productCode == \"WIDGET\"\n        rtn: 29.99\n      else if productCode == \"GADGET\"\n        rtn: 49.99\n\n  defines program\n\n    IsSetWithGuardDemo()\n      stdout <- Stdout()\n\n      widgetCode <- \"WIDGET\"\n      gadgetCode <- \"GADGET\"\n      mysteryCode <- \"MYSTERY\"\n\n      // ? suffix on variable that might be unset\n      maybePrice <- lookupPrice(widgetCode)\n      if maybePrice?\n        stdout.println(`Found price: ${maybePrice}`)\n\n      // Guard declares + checks in one step (equivalent but more concise)\n      if gadgetPrice <- lookupPrice(gadgetCode)\n        stdout.println(`Gadget: ${gadgetPrice}`)\n\n      // Guard with unset result — block skipped\n      if unknownPrice <- lookupPrice(mysteryCode)\n        stdout.println(\"Should not print\")\n      else\n        stdout.println(\"Product not found\")\n\n      // ? on unset — no guard needed since variable exists\n      emptyPrice <- Float()\n      if emptyPrice?\n        stdout.println(\"Should not print\")\n      else\n        stdout.println(\"Price is unset\")","migrationContext":"EK9 has two patterns: variable? checks an existing variable, 'if v <- expr()' guard declares and checks in one step. Use ? when you already have the variable, guards when declaring.","keywords":["combine","declaration guard","guard","isSet","suffix"],"primaryTopics":["? with guards","isSet and guard combination"],"typicalErrors":[{"error":"E01073","correct":"      if gadgetPrice <- lookupPrice(gadgetCode)\n        stdout.println(`Gadget: ${gadgetPrice}`)","incorrect":"      gadgetPrice <- lookupPrice(gadgetCode)\n      if gadgetPrice != null\n        stdout.println(`Gadget: ${$gadgetPrice}`)","explanation":"'null' does not exist in EK9. Use a guard 'if v <- expr()' to declare and check in one step. See ek9 -h E01073."}],"companions":[]}
{"id":983,"category":"Debugging and Troubleshooting","question":"What are the most common EK9 compiler errors and what do they mean?","url":"https://ek9.io/qa/QA0983.html","alternatePhrasings":["Which EK9 error codes do I see most often?","What do E50001, E50060, and E05030 mean in EK9?","Quick reference for common EK9 compiler errors"],"answer":"The most common EK9 compiler errors:\n\nE50001 NOT RESOLVED — a name (variable, type, or function) cannot be found. Likely a typo, missing import, or using := before <- to declare.\n\nE50060 METHOD NOT RESOLVED — calling a method that does not exist on the type. Check the type API with 'ek9 -h TypeName'.\n\nE05030 NOT OPEN TO EXTENSION — trying to extend a class that is closed by default. Add 'as open' to the base class or use composition.\n\nE01072 RETURN NOT SUPPORTED — using 'return' which does not exist in EK9. Declare return variable with <- and the compiler ensures all paths initialise it.\n\nE01073 NULL NOT SUPPORTED — using 'null' which does not exist in EK9. Use tri-state semantics with ? operator to check if a value is set.\n\nE11031 NON DESCRIPTIVE NAME — using a banned variable name like 'temp', 'data', 'flag'. Use a descriptive name instead.\n\nE08030 UNSAFE ACCESS — accessing a value that might be unset without checking first. Use a guard 'if v <- expr()' or check with v? before access.\n\nUse 'ek9 -h Exxxxx' for detailed explanation of any error code.","ek9Example":"defines module qa.debugging.commonerrors\n\n  defines function\n\n    safeDivide() as pure\n      ->\n        numerator as Float\n        denominator as Float\n      <- rtn as Float: Float()\n\n      if denominator?\n        rtn: numerator / denominator\n\n  defines program\n\n    CommonErrorsDemo()\n      stdout <- Stdout()\n\n      // Correct: <- to declare, := to update\n      greeting <- \"Hello\"\n      greeting := \"Hi there\"\n      stdout.println(greeting)\n\n      // Correct: ? to check before use\n      answer <- safeDivide(10.0, 3.0)\n      if answer?\n        stdout.println(`Result: ${answer}`)\n\n      // Correct: guard instead of null check\n      if safeAnswer <- safeDivide(10.0, 0.0)\n        stdout.println(`Got: ${safeAnswer}`)\n      else\n        stdout.println(\"Division returned unset\")","migrationContext":"Java developers most often hit E05030 (closed types) and E01072 (no return). Python developers hit E50001 (typos) and E11031 (banned names). All hit E01073 (no null) initially.","keywords":["E05030","E50001","E50060","common","debug","error","troubleshoot"],"primaryTopics":["common error codes","error code quick reference"],"typicalErrors":[{"error":"E50001","correct":"      greeting <- \"Hello\"","incorrect":"      greeting := \"Hello\"","explanation":"E50001 means the name is not resolved. If you used := before <-, the variable does not exist yet. Use <- to declare it first."}],"companions":[]}
{"id":984,"category":"Control Flow","question":"How do I assign a value based on a condition in EK9?","url":"https://ek9.io/qa/QA0984.html","alternatePhrasings":["How do I choose between two values based on a condition in EK9?","What replaces conditional value assignment in EK9?","How do I write a one-line conditional in EK9?","How do I assign different values depending on a test?"],"answer":"EK9 provides three patterns for conditional value assignment:\n\nPATTERN 1: IF/ELSE ASSIGNMENT\n  label <- \"standard\"\n  if score > threshold\n    label := \"premium\"\n\nPATTERN 2: SWITCH EXPRESSION\n  category <- switch rating\n    <- rtn as String?\n    case > highMark\n      rtn: \"excellent\"\n    case > passMark\n      rtn: \"good\"\n    default\n      rtn: \"needs improvement\"\n\nPATTERN 3: COALESCING (?? and :=?)\n  displayName <- userName ?? \"Guest\"\n  fallback :=? computeDefault()\n\nChoose if/else for simple binary conditions. Choose switch expression for multiple cases. Choose ?? or :=? when providing defaults for unset values.","ek9Example":"defines module qa.controlflow.conditionalvalue\n\n  defines function\n\n    classifyTemperature() as pure\n      -> celsius as Float\n      <- rtn as String?\n\n      coldThreshold <- 10.0\n      hotThreshold <- 30.0\n\n      rtn: switch celsius\n        <- category as String?\n        case < coldThreshold\n          category: \"cold\"\n        case > hotThreshold\n          category: \"hot\"\n        default\n          category: \"mild\"\n\n  defines program\n\n    ConditionalValueDemo()\n      stdout <- Stdout()\n\n      // Pattern 1: if/else assignment\n      temperature <- classifyTemperature(15.0)\n      descriptor <- \"unknown\"\n      if temperature?\n        descriptor := temperature\n      stdout.println(`Descriptor: ${descriptor}`)\n\n      // Pattern 2: switch expression\n      weather <- classifyTemperature(22.0)\n      stdout.println(`Weather: ${weather}`)\n\n      // Pattern 3: coalescing for defaults\n      nickname <- String()\n      displayName <- nickname ?? \"Anonymous\"\n      stdout.println(`Name: ${displayName}`)","migrationContext":"Other languages use a conditional expression for this. EK9 uses if/else assignment, switch expressions, or the ?? coalescing operator depending on the scenario.","keywords":["assign","choose","conditional","if else","switch expression","value"],"primaryTopics":["conditional value assignment","choosing between values"],"typicalErrors":[{"error":"E01010","correct":"      descriptor <- \"unknown\"\n      if temperature?\n        descriptor := temperature","incorrect":"      descriptor <- temperature? ? temperature : \"unknown\"","explanation":"The ? in EK9 is the isSet suffix operator, not a conditional expression. This line does not parse. Use if/else assignment instead."},{"error":"E01010","correct":"      weather <- classifyTemperature(22.0)","incorrect":"      weather <- if classifyTemperature(22.0) then classifyTemperature(22.0) else \"unknown\"","explanation":"EK9 does not have an inline if expression. Use a function call with a guard, or if/else assignment on separate lines."}],"companions":[]}
{"id":985,"category":"Control Flow","question":"How do I provide a default value when something might be unset in EK9?","url":"https://ek9.io/qa/QA0985.html","alternatePhrasings":["How do I fall back to a default in EK9?","What is the EK9 equivalent of a default value expression?","How does ?? work for defaults in EK9?","How do I use :=? for conditional initialisation?"],"answer":"EK9 provides two operators for default values:\n\n?? VALUE COALESCING\nReturns the left value if SET, otherwise the right:\n  displayName <- userName ?? \"Guest\"\n  timeout <- configuredTimeout ?? defaultTimeout\n\n:=? GUARDED ASSIGNMENT\nOnly assigns if the target is currently UNSET:\n  serverHost <- String()\n  serverHost :=? \"localhost\"\n  serverHost :=? \"other\"     has no effect — already set to localhost\n\nWHEN TO USE EACH\n?? when creating a new variable with a fallback.\n:=? when conditionally initialising an existing variable.\n\nCOMBINING WITH GUARDS\n  if connection <- tryConnect(primaryHost)\n    useConnection(connection)\n  else\n    fallbackHost <- primaryHost ?? backupHost\n    if backup <- tryConnect(fallbackHost)\n      useConnection(backup)","ek9Example":"defines module qa.controlflow.defaultvalue\n\n  defines function\n\n    lookupSetting() as pure\n      -> settingName as String\n      <- rtn as String: String()\n\n      if settingName == \"host\"\n        rtn: \"production.example.com\"\n\n  defines program\n\n    DefaultValueDemo()\n      stdout <- Stdout()\n\n      // ?? for inline defaults\n      configuredHost <- lookupSetting(\"host\")\n      activeHost <- configuredHost ?? \"localhost\"\n      stdout.println(`Host: ${activeHost}`)\n\n      // ?? when lookup returns unset\n      missingPort <- lookupSetting(\"port\")\n      activePort <- missingPort ?? \"8080\"\n      stdout.println(`Port: ${activePort}`)\n\n      // :=? for conditional initialisation\n      logLevel <- String()\n      logLevel :=? \"INFO\"\n      stdout.println(`Log level: ${logLevel}`)\n\n      // :=? has no effect when already set\n      logLevel :=? \"DEBUG\"\n      stdout.println(`Still: ${logLevel}`)","migrationContext":"EK9 uses ?? for value coalescing and :=? for guarded assignment. Both handle unset values without any concept of null.","keywords":["coalescing","default","fallback","guarded","unset"],"primaryTopics":["default values","?? coalescing",":=? guarded assignment"],"typicalErrors":[{"error":"E01010","correct":"      activeHost <- configuredHost ?? \"localhost\"","incorrect":"      activeHost <- configuredHost != null ? configuredHost : \"localhost\"","explanation":"'null' does not exist in EK9 and the ? symbol is the isSet suffix, not a conditional. Use ?? for value coalescing: left ?? right returns left if set, else right."},{"error":"E01010","correct":"      logLevel :=? \"INFO\"","incorrect":"      logLevel <- logLevel ? logLevel : \"INFO\"","explanation":"The ? is the isSet suffix, not part of a conditional expression. Use :=? for guarded assignment — it only assigns if the target is currently unset."}],"companions":[]}
{"id":986,"category":"Getting Started","question":"How does EK9's := differ from Python's := walrus operator?","url":"https://ek9.io/qa/QA0986.html","alternatePhrasings":["Is EK9's := the same as Python's walrus operator?","Can I use := inside an if condition in EK9?","Why can't I write 'if n := getValue()' in EK9?","How do I assign and test a value in one step in EK9?"],"answer":"EK9's := and Python's := (walrus) look identical but behave completely differently.\n\nPYTHON := (walrus) is an ASSIGNMENT EXPRESSION:\n- Assigns a value AND evaluates to that value\n- Can be used INSIDE conditions: if (n := len(data)) > 3:\n- Creates the variable on first use\n- Returns the assigned value for further use in the expression\n\nEK9 := is an ASSIGNMENT STATEMENT:\n- Assigns to an EXISTING variable (must be declared first with <-)\n- Cannot be used inside expressions or conditions\n- Does not return a value\n- Is a standalone statement only\n\nTO ASSIGN AND TEST IN EK9:\nUse the <- guard declaration, not :=\n  if n <- computeLength()\n    use(n)\nThe guard declares n AND checks if it is set. This is EK9's equivalent of Python's walrus pattern, but uses <- not :=.\n\nQUICK REFERENCE:\n  Python:  if (n := len(data)) > 3:       walrus assigns + evaluates\n  EK9:     if n <- computeLength() then n > 3    guard declares + checks isSet\n\n  Python:  x := 5  (inside expression)     walrus, creates variable\n  EK9:     x <- 5  (standalone)            declaration, creates variable\n  EK9:     x := 5  (standalone)            assignment, updates existing variable","ek9Example":"defines module qa.gettingstarted.walrusvsassign\n\n  defines function\n\n    computeLength() as pure\n      -> items as List of String\n      <- rtn as Integer: items.length()\n\n  defines program\n\n    WalrusVsAssignDemo()\n      stdout <- Stdout()\n\n      words <- [\"hello\", \"world\", \"ek9\", \"language\"]\n      minimumSize <- 2\n\n      // EK9 guard pattern (replaces Python walrus in conditions)\n      if wordCount <- computeLength(words) then wordCount > minimumSize\n        stdout.println(`Has ${wordCount} words - enough to process`)\n\n      // := is for updating existing variables (standalone statement only)\n      message <- \"initial\"\n      message := \"updated\"\n      stdout.println(message)\n\n      // <- declares, := updates — both are STATEMENTS, never expressions\n      counter <- 0\n      counter := counter + 1\n      stdout.println(`Counter: ${counter}`)","migrationContext":"Python developers moving to EK9: your := walrus patterns become <- guard expressions. EK9's := only works as a standalone statement for updating existing variables.","keywords":["assign","condition","expression","guard","statement","walrus"],"primaryTopics":[":= vs walrus","assignment expression vs statement"],"typicalErrors":[{"error":"E01010","correct":"      if wordCount <- computeLength(words) then wordCount > minimumSize\n        stdout.println(`Has ${wordCount} words - enough to process`)","incorrect":"      if (wordCount := computeLength(words)) > minimumSize\n        stdout.println(`Has ${$wordCount} words - enough to process`)","explanation":"EK9's := is a statement, not an expression. It cannot be used inside conditions. Use the <- guard pattern: 'if v <- expr() then condition'."}],"companions":[]}
{"id":987,"category":"Streams and Pipelines","question":"What does this stream pipeline do? Trace the data flow step by step.","url":"https://ek9.io/qa/QA0987.html","alternatePhrasings":["Explain what happens at each stage of this EK9 pipeline","Walk me through how data flows through this stream","Break down this EK9 stream pipeline for me"],"answer":"This pipeline processes a list of order amounts through four stages:\n\n1. cat orderAmounts — reads each Float from the list, one at a time\n2. filter by isLargeOrder — keeps only amounts above the threshold, discards the rest\n3. sort by compareDescending — orders the remaining amounts from largest to smallest\n4. head 3 — takes only the first 3 items, then stops the pipeline\n5. > stdout — prints each of the 3 largest orders to standard output\n\nThe data flows left to right through each | operator. Each stage receives items from the previous stage and passes results to the next. The head operation acts like a limit — once 3 items have passed through, the pipeline terminates early without processing remaining items.","ek9Example":"defines module qa.streams.explainpipeline\n\n  defines function\n\n    isLargeOrder() as pure\n      -> amount as Float\n      <- rtn as Boolean?\n\n      threshold <- 100.0\n      rtn: amount > threshold\n\n    compareDescending() as pure\n      ->\n        left as Float\n        right as Float\n      <- rtn as Integer: right <=> left\n\n  defines program\n\n    ExplainPipelineDemo()\n      stdout <- Stdout()\n\n      orderAmounts <- [45.99, 250.00, 89.50, 175.00, 320.00, 15.00, 199.99]\n\n      stdout.println(\"Top 3 large orders:\")\n      cat orderAmounts\n        | filter by isLargeOrder\n        | sort by compareDescending\n        | head 3\n        > stdout","migrationContext":"EK9 stream pipelines read left-to-right like Unix pipes. Each | passes data to the next operation. The > terminal sends output to a sink.","keywords":["data flow","explain","investigate","pipeline","stream","trace"],"primaryTopics":["stream pipeline explanation","data flow tracing"],"typicalErrors":[{"error":"E50001","correct":"      cat orderAmounts\n        | filter by isLargeOrder\n        | sort by compareDescending\n        | head 3\n        > stdout","incorrect":"      orderAmounts.stream().filter(isLargeOrder).sorted(compareDescending).limit(3).forEach(System.out::println)","explanation":"EK9 uses Unix pipe syntax for streams, not Java method chaining. Write 'cat source | filter by fn | sort by fn | head N > stdout'."}],"companions":[]}
{"id":988,"category":"Streams and Pipelines","question":"Review this EK9 code. Should the loop be a stream pipeline instead?","url":"https://ek9.io/qa/QA0988.html","alternatePhrasings":["Is this loop idiomatic EK9 or should it use a stream?","Assess whether this code follows EK9 best practices","This code works but is it the EK9 way?"],"answer":"The code uses a for loop with an if condition inside to filter and collect results. This works but is not idiomatic EK9.\n\nIDIOMATIC VERSION:\n  highScores <- cat allScores\n    | filter by isAboveCutoff\n    | collect as List of Integer\n\nWHY THE STREAM IS BETTER:\n- The intent is clearer — 'filter by isAboveCutoff' states what happens\n- No mutable accumulator variable needed\n- The filter predicate is a named, testable, reusable function\n- The pipeline reads as a data transformation, not imperative steps\n\nWHEN LOOPS ARE APPROPRIATE:\n- When you need mutation or side effects at each step\n- When the operation is inherently sequential with state between iterations\n- When the body does I/O per item (though streams with > stdout handle this too)\n\nIn this case, filtering and collecting is a pure data transformation — a stream pipeline is the idiomatic choice.","ek9Example":"defines module qa.streams.reviewloopvsstream\n\n  defines function\n\n    isAboveCutoff() as pure\n      -> score as Integer\n      <- rtn as Boolean?\n\n      passingScore <- 70\n      rtn: score > passingScore\n\n  defines program\n\n    ReviewLoopDemo()\n      stdout <- Stdout()\n\n      allScores <- [85, 42, 91, 67, 73, 55, 88, 96, 61, 79]\n\n      //Idiomatic: use stream pipeline for filter + collect\n      highScores <- cat allScores\n        | filter by isAboveCutoff\n        | collect as List of Integer\n\n      stdout.println(`High scores: ${highScores}`)\n      stdout.println(`Count: ${highScores.length()}`)","migrationContext":"EK9 uses stream pipelines for filtering, transformation, and collection. Loops are for mutation and sequential stateful operations.","keywords":["best practice","idiomatic","loop","quality","review","stream"],"primaryTopics":["code review","loop vs stream","idiomatic EK9"],"typicalErrors":[{"error":"E01010","correct":"      highScores <- cat allScores\n        | filter by isAboveCutoff\n        | collect as List of Integer","incorrect":"      highScores <- allScores.stream().filter(s -> s > cutoff).collect(Collectors.toList())","explanation":"EK9 streams use pipe syntax, not Java method chaining. Write 'cat source | filter by fn | collect as Type'."}],"companions":[]}
{"id":989,"category":"Streams and Pipelines","question":"How do I group items and then flatten them back in an EK9 stream?","url":"https://ek9.io/qa/QA0989.html","alternatePhrasings":["Show me group by and flatten in an EK9 stream pipeline","How do I process grouped data then flatten results in EK9?","What does sort by then group by then flatten do in a pipeline?"],"answer":"Group collects items into sub-lists by a key. Flatten expands those sub-lists back into individual items.\n\nIMPORTANT: sort BEFORE group. Items must be sorted by the grouping key before grouping. The compiler enforces this with E10040.\n\nPATTERN:\n  cat items | sort by keyComparator | group by keyExtractor | flatten > stdout\n\nSTEP BY STEP:\n1. sort by — orders items so equal keys are adjacent\n2. group by — collects adjacent equal-key items into Lists\n3. (optional processing on groups — filter with, map by)\n4. flatten — expands each List back to individual items\n\nWithout flatten, you get a stream of Lists. With flatten, you get a stream of the original item type.","ek9Example":"defines module qa.streams.groupflatten\n\n  defines record\n\n    Employee\n      employeeName as String: String()\n      department as String: String()\n      salary as Float: 0.0\n\n      Employee()\n        ->\n          employeeName as String\n          department as String\n          salary as Float\n        this.employeeName :=: employeeName\n        this.department :=: department\n        this.salary :=: salary\n\n      default operator\n\n  defines function\n\n    compareDepartment() as pure\n      ->\n        left as Employee\n        right as Employee\n      <- rtn as Integer: left.department <=> right.department\n\n    extractDepartment() as pure\n      -> employee as Employee\n      <- rtn as String: employee.department\n\n  defines program\n\n    GroupFlattenDemo()\n      stdout <- Stdout()\n\n      employees <- [\n        Employee(\"Alice\", \"Engineering\", 95000.0),\n        Employee(\"Bob\", \"Marketing\", 72000.0),\n        Employee(\"Charlie\", \"Engineering\", 88000.0),\n        Employee(\"Diana\", \"Marketing\", 81000.0),\n        Employee(\"Eve\", \"Engineering\", 102000.0)\n      ]\n\n      //Sort by department, group, then flatten back\n      stdout.println(\"Employees grouped by department:\")\n      cat employees\n        | sort by compareDepartment\n        | group by extractDepartment\n        | flatten\n        > stdout","migrationContext":"EK9 requires sort before group — the grouping algorithm assumes sorted input. This is enforced at compile time, unlike Java's Collectors.groupingBy which sorts internally.","keywords":["flatten","group","pipeline","sort","stream"],"primaryTopics":["stream group flatten","sort before group"],"typicalErrors":[{"error":"E10040","correct":"      cat employees\n        | sort by compareDepartment\n        | group by extractDepartment","incorrect":"      cat employees\n        | group by extractDepartment","explanation":"EK9 requires sort before group. The grouping algorithm needs sorted input to collect adjacent equal-key items. Add sort by before group by. See ek9 -h E10040."}],"companions":[]}
{"id":990,"category":"Streams and Pipelines","question":"I need to find the cheapest item in each category. How would I approach this in EK9?","url":"https://ek9.io/qa/QA0990.html","alternatePhrasings":["What EK9 constructs would I use to find minimums per group?","How do I decompose a group-and-find-minimum task in EK9?","Plan an approach for finding the best value per category"],"answer":"APPROACH:\n1. Sort products by category (required before grouping)\n2. Group by category — produces a stream of Lists, one per category\n3. Map each group to find its minimum-priced item\n4. Collect the results or output directly\n\nSTREAM PIPELINE:\n  cat products\n    | sort by compareCategory\n    | group by extractCategory\n    | map with findCheapestInGroup\n    | collect as List of Product\n\nFUNCTIONS NEEDED:\n- compareCategory: compares two Products by their category field\n- extractCategory: extracts the category String from a Product\n- findCheapestInGroup: takes a List of Product, returns the one with lowest price\n\nThe findCheapestInGroup function itself can use a stream:\n  cat group | sort by comparePrice | head 1 | collect as List of Product\n\nThis decomposes a complex task into small, testable, pure functions connected by a pipeline.","ek9Example":"defines module qa.streams.planapproach\n\n  defines record\n\n    Product\n      productName as String: String()\n      category as String: String()\n      price as Float: 0.0\n\n      Product()\n        ->\n          productName as String\n          category as String\n          price as Float\n        this.productName :=: productName\n        this.category :=: category\n        this.price :=: price\n\n      default operator\n\n  defines function\n\n    compareCategory() as pure\n      ->\n        left as Product\n        right as Product\n      <- rtn as Integer: left.category <=> right.category\n\n    extractCategory() as pure\n      -> product as Product\n      <- rtn as String: product.category\n\n    comparePrice() as pure\n      ->\n        left as Product\n        right as Product\n      <- rtn as Integer: left.price <=> right.price\n\n  defines program\n\n    PlanApproachDemo()\n      stdout <- Stdout()\n\n      products <- [\n        Product(\"Widget\", \"Tools\", 29.99),\n        Product(\"Gadget\", \"Electronics\", 149.99),\n        Product(\"Wrench\", \"Tools\", 15.50),\n        Product(\"Phone\", \"Electronics\", 899.00),\n        Product(\"Hammer\", \"Tools\", 22.00),\n        Product(\"Tablet\", \"Electronics\", 349.00)\n      ]\n\n      //Sort by category, group, then output grouped\n      stdout.println(\"Products by category:\")\n      cat products\n        | sort by compareCategory\n        | group by extractCategory\n        > stdout","migrationContext":"EK9 decomposes group-and-reduce tasks into sort, group, map steps connected by a pipeline. Each step is a named pure function.","keywords":["approach","decompose","group","minimum","plan","stream"],"primaryTopics":["stream pipeline planning","task decomposition"],"typicalErrors":[{"error":"E10040","correct":"      cat products\n        | sort by compareCategory\n        | group by extractCategory","incorrect":"      cat products\n        | group by extractCategory","explanation":"Must sort before group in EK9. The grouping algorithm requires sorted input. See ek9 -h E10040."}],"companions":[]}
{"id":991,"category":"Control Flow","question":"Trace the execution flow through these guard expressions. What values get printed?","url":"https://ek9.io/qa/QA0991.html","alternatePhrasings":["Walk through this EK9 code and predict the output","What does each guard expression do in this code?","Explain the control flow when guards encounter unset values"],"answer":"Tracing the execution:\n\n1. fetchTemperature(\"London\") returns 18.5 (set). Guard succeeds.\n   Prints: 'London: 18.5C'\n\n2. fetchTemperature(\"Atlantis\") returns unset (unknown city). Guard fails.\n   The if block is skipped. The else block runs.\n   Prints: 'Atlantis: no reading available'\n\n3. fetchTemperature(\"Dubai\") returns 42.0 (set). Guard succeeds AND 42.0 > heatThreshold (35.0).\n   The then condition passes.\n   Prints: 'Dubai heat warning: 42.0C'\n\n4. fetchTemperature(\"Oslo\") returns 3.0 (set). Guard succeeds BUT 3.0 is NOT > heatThreshold.\n   The then condition fails. The entire if block is skipped.\n   No output for Oslo.\n\nGuards combine declaration + isSet check in one step. The 'then' clause adds an additional condition that must also be true.","ek9Example":"defines module qa.controlflow.explainguardflow\n\n  defines function\n\n    fetchTemperature() as pure\n      -> cityName as String\n      <- rtn as Float: Float()\n\n      londonName <- \"London\"\n      dubaiName <- \"Dubai\"\n      osloName <- \"Oslo\"\n\n      if cityName == londonName\n        rtn: 18.5\n      else if cityName == dubaiName\n        rtn: 42.0\n      else if cityName == osloName\n        rtn: 3.0\n\n  defines program\n\n    GuardFlowDemo()\n      stdout <- Stdout()\n\n      heatThreshold <- 35.0\n\n      //Guard succeeds — London returns a set value\n      if londonTemp <- fetchTemperature(\"London\")\n        stdout.println(`London: ${londonTemp}C`)\n\n      //Guard fails — Atlantis returns unset\n      if atlantisTemp <- fetchTemperature(\"Atlantis\")\n        stdout.println(`Atlantis: ${atlantisTemp}C`)\n      else\n        stdout.println(\"Atlantis: no reading available\")\n\n      //Guard with then — Dubai is set AND above threshold\n      if dubaiTemp <- fetchTemperature(\"Dubai\") then dubaiTemp > heatThreshold\n        stdout.println(`Dubai heat warning: ${dubaiTemp}C`)\n\n      //Guard with then — Oslo is set but NOT above threshold\n      if osloTemp <- fetchTemperature(\"Oslo\") then osloTemp > heatThreshold\n        stdout.println(`Oslo heat warning: ${osloTemp}C`)","migrationContext":"EK9 guards replace null checks from other languages. The if v <- expr() pattern declares, checks isSet, and optionally tests a condition in one atomic step.","keywords":["execution","flow","guard","output","predict","trace"],"primaryTopics":["guard expression tracing","execution flow prediction"],"typicalErrors":[{"error":"E01073","correct":"      if londonTemp <- fetchTemperature(\"London\")\n        stdout.println(`London: ${londonTemp}C`)","incorrect":"      londonTemp <- fetchTemperature(\"London\")\n      if londonTemp != null\n        stdout.println(`London: ${$londonTemp}C`)","explanation":"'null' does not exist in EK9. Use the guard pattern 'if v <- expr()' to combine declaration with isSet checking. See ek9 -h E01073."}],"companions":[]}
{"id":992,"category":"Code Quality","question":"Review this EK9 class. Does it follow EK9 best practices?","url":"https://ek9.io/qa/QA0992.html","alternatePhrasings":["Assess the quality of this EK9 class design","What could be improved about this class?","Is this class well-designed by EK9 standards?"],"answer":"This class follows several EK9 best practices:\n\nGOOD:\n- Fields are private (class default) with accessor methods\n- Constructor uses -> for parameters and assigns with :\n- 'default operator' generates standard operators from fields\n- The describe() method returns a value rather than printing (pure)\n- Named accessor methods match field names (accountHolder, currentBalance)\n\nIMPROVEMENT OPPORTUNITIES:\n- The deposit and withdraw methods could validate inputs (e.g., negative amounts)\n- The class could use 'as pure' on accessor methods to declare they have no side effects\n- Consider using :=? guarded assignment in withdraw to prevent negative balance\n- If this class is not meant to be extended, the default closed status is correct\n\nOVERALL: Good basic design. The separation of data (private fields) from behaviour (methods) is correct EK9 style.","ek9Example":"defines module qa.codequality.reviewclass\n\n  defines class\n\n    BankAccount\n      accountHolder as String: String()\n      currentBalance as Float: 0.0\n\n      BankAccount()\n        ->\n          accountHolder as String\n          currentBalance as Float\n        this.accountHolder: accountHolder\n        this.currentBalance: currentBalance\n\n      accountHolder() as pure\n        <- rtn as String: accountHolder\n\n      currentBalance() as pure\n        <- rtn as Float: currentBalance\n\n      deposit()\n        -> depositAmount as Float\n        currentBalance := currentBalance + depositAmount\n\n      withdraw()\n        -> withdrawalAmount as Float\n        currentBalance := currentBalance - withdrawalAmount\n\n      describe() as pure\n        <- rtn as String: `${accountHolder}: ${currentBalance}`\n\n      default operator\n\n  defines program\n\n    ReviewClassDemo()\n      stdout <- Stdout()\n\n      account <- BankAccount(\"Alice Smith\", 1000.0)\n      stdout.println(account.describe())\n\n      account.deposit(500.0)\n      stdout.println(account.describe())\n\n      account.withdraw(200.0)\n      stdout.println(account.describe())","migrationContext":"EK9 classes follow encapsulation by default — fields are private, methods provide access. Use 'default operator' to auto-generate standard operators.","keywords":["assess","best practice","class","design","quality","review"],"primaryTopics":["class design review","EK9 best practices"],"typicalErrors":[{"error":"E06180","correct":"      stdout.println(account.describe())","incorrect":"      stdout.println(account.accountHolder)","explanation":"Class fields are private in EK9. Access them through methods: account.describe() or account.accountHolder(). Direct field access like account.accountHolder only works on records."}],"companions":[]}
{"id":993,"category":"Debugging and Troubleshooting","question":"The compiler reports E08030 on this method. What is wrong and how do I fix it?","url":"https://ek9.io/qa/QA0993.html","alternatePhrasings":["Diagnose this E08030 error in my EK9 code","Why is the compiler saying unsafe access on this line?","Help me understand and fix E08030 in this code"],"answer":"E08030 means you are accessing a value that might be unset without checking first. The compiler tracks which variables are guaranteed to be set at each point in the code.\n\nIN THIS CODE:\nThe lookupDiscount function returns a Float that might be unset (returns Float() for unknown products). The calling code uses the result directly without checking if it is set.\n\nTHE FIX:\nUse a guard expression to check isSet before accessing the value:\n  if discountRate <- lookupDiscount(productCode)\n    finalPrice := basePrice - (basePrice * discountRate)\n\nThe guard 'if discountRate <- lookupDiscount(productCode)' declares discountRate AND checks if it is set. The body only runs when the discount is available.\n\nALTERNATIVELY:\nUse the ?? coalescing operator for a default:\n  discountRate <- lookupDiscount(productCode) ?? 0.0\n  finalPrice := basePrice - (basePrice * discountRate)\n\nThis assigns 0.0 (no discount) when the lookup returns unset.","ek9Example":"defines module qa.debugging.diagnoseerror\n\n  defines function\n\n    lookupDiscount() as pure\n      -> productCode as String\n      <- rtn as Float: Float()\n\n      premiumCode <- \"PREMIUM\"\n      standardCode <- \"STANDARD\"\n\n      if productCode == premiumCode\n        rtn: 0.20\n      else if productCode == standardCode\n        rtn: 0.10\n\n  defines program\n\n    DiagnoseErrorDemo()\n      stdout <- Stdout()\n\n      basePrice <- 100.0\n\n      //Correct: use guard to check if discount exists\n      if discountRate <- lookupDiscount(\"PREMIUM\")\n        finalPrice <- basePrice - (basePrice * discountRate)\n        stdout.println(`Discounted price: ${finalPrice}`)\n\n      //Correct: use ?? for default value\n      standardDiscount <- lookupDiscount(\"UNKNOWN\") ?? 0.0\n      regularPrice <- basePrice - (basePrice * standardDiscount)\n      stdout.println(`Regular price: ${regularPrice}`)","migrationContext":"E08030 in EK9 is similar to 'potential null dereference' warnings in other languages, but in EK9 it is a hard compile error, not a warning.","keywords":["E08030","access","diagnose","fix","guard","unsafe"],"primaryTopics":["E08030 diagnosis","unsafe access fix"],"typicalErrors":[{"error":"E01073","correct":"      if discountRate <- lookupDiscount(\"PREMIUM\")\n        finalPrice <- basePrice - (basePrice * discountRate)\n        stdout.println(`Discounted price: ${finalPrice}`)","incorrect":"      discountRate <- lookupDiscount(\"PREMIUM\")\n      if discountRate != null\n        finalPrice <- basePrice - (basePrice * discountRate)\n        stdout.println(`Discounted price: ${$finalPrice}`)","explanation":"'null' does not exist in EK9. Use a guard 'if v <- expr()' to check isSet, or the ? suffix: 'if discountRate?'. See ek9 -h E01073."}],"companions":[]}
{"id":994,"category":"Operators and Expressions","question":"How do I convert a value to a String in EK9?","url":"https://ek9.io/qa/QA0994.html","alternatePhrasings":["What does the $ prefix do to a variable in EK9?","How do I get the string representation of an Integer or Float?","Show me how to convert numbers to strings in EK9"],"answer":"Prefix any variable with $ to convert it to a String. The $ operator calls the _string() method on the object.\n\nEXAMPLES:\n  age <- 25\n  ageText <- $age                converts Integer 25 to String \"25\"\n\n  price <- 19.99\n  priceText <- $price            converts Float 19.99 to String \"19.99\"\n\n  active <- true\n  activeText <- $active          converts Boolean to String \"true\"\n\nINSIDE BACKTICK STRINGS:\nWithin backtick strings, ${expression} evaluates the expression and converts to String automatically:\n  stdout.println(`Age: ${$age}`)   explicit $ conversion inside ${}\n  stdout.println(`Age: ${age}`)    implicit conversion — same result for String types\n\nThe $ prefix and ${} backtick syntax are different things:\n  $variable      standalone conversion — returns a String value\n  ${expression}  backtick syntax — embeds expression result in a string","ek9Example":"defines module qa.operators.dollarconverts\n\n  defines program\n\n    DollarConvertsDemo()\n      stdout <- Stdout()\n\n      // $ converts Integer to String\n      age <- 25\n      ageText <- $age\n      stdout.println(`Age as String: ${ageText}`)\n\n      // $ converts Float to String\n      price <- 19.99\n      priceText <- $price\n      stdout.println(`Price as String: ${priceText}`)\n\n      // $ converts Boolean to String\n      isActive <- true\n      activeText <- $isActive\n      stdout.println(`Active as String: ${activeText}`)\n\n      // $ on a String is identity — returns the same String\n      greeting <- \"Hello\"\n      sameGreeting <- $greeting\n      stdout.println(sameGreeting)\n\n      // Use $ when you need an explicit String for concatenation or assignment\n      itemCount <- 42\n      summary <- \"Items: \" + $itemCount\n      stdout.println(summary)","migrationContext":"EK9 uses $variable as a prefix conversion operator. It calls _string() and returns a String.","keywords":["convert","dollar","prefix","string","toString"],"primaryTopics":["$ string conversion","value to string"],"typicalErrors":[{"error":"E50060","correct":"      ageText <- $age","incorrect":"      ageText <- age.toString()","explanation":"EK9 has no toString() method. Use the $ prefix operator: $variable converts any value to String by calling _string()."}],"companions":[]}
{"id":995,"category":"Operators and Expressions","question":"How do I make my own class work with the $ operator in EK9?","url":"https://ek9.io/qa/QA0995.html","alternatePhrasings":["How do I define a custom string conversion for my EK9 class?","What method does $ call on my class?","How do I control what $myObject produces in EK9?"],"answer":"The $ operator calls the _string() method. To customise it for your own class, either use 'default operator' (auto-generates from fields) or implement operator $ yourself.\n\nUSING DEFAULT OPERATOR:\n  default operator\nThis auto-generates $ (and other operators) from your class fields.\n\nCUSTOM IMPLEMENTATION:\n  operator $ as pure\n    <- rtn as String: `${accountHolder}: ${$currentBalance}`\n\nThe operator must be marked 'as pure', take no parameters, and return a String.\n\nUSAGE:\n  account <- BankAccount(\"Alice\", 1000.0)\n  description <- $account       calls your _string() method\n  stdout.println($account)      same — converts then prints","ek9Example":"defines module qa.operators.dollarcustomclass\n\n  defines class\n\n    TemperatureSensor\n      location as String: String()\n      currentReading as Float: 0.0\n\n      TemperatureSensor()\n        ->\n          location as String\n          currentReading as Float\n        this.location: location\n        this.currentReading: currentReading\n\n      //Custom $ operator — controls what $sensor produces\n      operator $ as pure\n        <- rtn as String: `${location}: ${currentReading}C`\n\n      default operator\n\n  defines program\n\n    DollarCustomDemo()\n      stdout <- Stdout()\n\n      sensor <- TemperatureSensor(\"Kitchen\", 22.5)\n\n      // $ calls our custom operator $\n      sensorText <- $sensor\n      stdout.println(sensorText)\n\n      // Same thing inline\n      stdout.println($sensor)","migrationContext":"EK9 uses 'operator $' to define string conversion. This replaces Java's toString(), Python's __str__(), Rust's Display trait.","keywords":["class","custom","dollar","operator","string","toString"],"primaryTopics":["custom $ operator","operator $ implementation"],"typicalErrors":[{"error":"E50060","correct":"      stdout.println($sensor)","incorrect":"      stdout.println(sensor.toString())","explanation":"EK9 has no toString() method. Use $variable to convert to String: stdout.println($sensor). The $ calls the operator $ (which calls _string()) on the object."}],"companions":[],"oracleToolHint":{"tool":"ek9_add_member","intent":"operator","description":"Oracle can add the $ operator to a custom class with correct return type."}}
{"id":996,"category":"Operators and Expressions","question":"Show me ++ and -- being used in a real EK9 program.","url":"https://ek9.io/qa/QA0996.html","alternatePhrasings":["How do I increment a counter in EK9?","Give me a practical example of ++ in EK9","How do I use ++ and += in EK9 code?"],"answer":"The ++ and -- operators increment and decrement a variable by 1. They are standalone statements — write them on their own line.\n\nINCREMENT:\n  counter++         adds 1 to counter\n\nDECREMENT:\n  counter--         subtracts 1 from counter\n\nOTHER MUTATION OPERATORS:\n  counter += 5      adds 5 to counter\n  counter -= 3      subtracts 3 from counter\n\nAll mutation operators are standalone statements. They modify the variable directly and cannot be combined with other expressions on the same line.","ek9Example":"defines module qa.operators.incrementpractice\n\n  defines program\n\n    IncrementDemo()\n      stdout <- Stdout()\n\n      // ++ increments by 1\n      hitCount <- 0\n      hitCount++\n      hitCount++\n      hitCount++\n      stdout.println(`Hits: ${hitCount}`)\n\n      // -- decrements by 1\n      livesRemaining <- 3\n      livesRemaining--\n      stdout.println(`Lives: ${livesRemaining}`)\n\n      // += adds an amount\n      totalScore <- 0\n      totalScore += 100\n      totalScore += 250\n      totalScore += 75\n      stdout.println(`Score: ${totalScore}`)\n\n      // -= subtracts an amount\n      totalScore -= 50\n      stdout.println(`After penalty: ${totalScore}`)\n\n      // Counting in a loop\n      evenCount <- 0\n      for number in 1 ... 10\n        if number mod 2 == 0\n          evenCount++\n      stdout.println(`Even numbers: ${evenCount}`)","migrationContext":"EK9 has ++ and -- as standalone statements. They modify the variable in place. Use += and -= for adding/subtracting other amounts.","keywords":["counter","decrement","increment","mutation","statement"],"primaryTopics":["++ operator usage","increment in practice"],"typicalErrors":[{"error":"E50050","correct":"      evenCount++","incorrect":"      evenCount <- evenCount++","explanation":"++ is a standalone statement in EK9 that does not return a value. Writing 'x <- x++' causes a duplicate variable error because ++ completes first, then <- tries to redeclare the same name."}],"companions":[]}
{"id":997,"category":"Functions and Methods","question":"How do I write a function that returns a value in EK9?","url":"https://ek9.io/qa/QA0997.html","alternatePhrasings":["What is the syntax for function return values in EK9?","How do I declare what a function returns in EK9?","Show me the <- rtn pattern for EK9 functions"],"answer":"Declare the return variable with <- in the function signature. The compiler ensures all paths initialise it. There is no return keyword.\n\nSINGLE RETURN:\n  calculateArea() as pure\n    -> radius as Float\n    <- rtn as Float: radius * radius * 3.14159\n\nThe <- rtn as Float declares a variable called 'rtn' that holds the return value. The caller receives whatever rtn contains when the function completes.\n\nCONDITIONAL RETURN:\n  classify() as pure\n    -> score as Integer\n    <- rtn as String: String()\n\n    passingScore <- 50\n    if score >= passingScore\n      rtn: \"pass\"\n    else\n      rtn: \"fail\"\n\nThe compiler verifies that rtn is assigned on ALL paths. If any path leaves rtn unset, you get a compile error.\n\nUNSET RETURN:\nReturning an unset value is valid — the caller checks with ? or a guard:\n  findUser() as pure\n    -> userId as Integer\n    <- rtn as String: String()\n    //rtn stays unset if no match found","ek9Example":"defines module qa.functionsandmethods.returnpattern\n\n  defines function\n\n    calculateArea() as pure\n      -> radius as Float\n      <- rtn as Float: radius * radius * 3.14159\n\n    celsiusToFahrenheit() as pure\n      -> celsius as Float\n      <- rtn as Float: (celsius * 9.0 / 5.0) + 32.0\n\n    classify() as pure\n      -> score as Integer\n      <- rtn as String: String()\n\n      passingScore <- 50\n      if score >= passingScore\n        rtn: \"pass\"\n      else\n        rtn: \"fail\"\n\n  defines program\n\n    ReturnPatternDemo()\n      stdout <- Stdout()\n\n      area <- calculateArea(5.0)\n      stdout.println(`Area: ${area}`)\n\n      fahrenheit <- celsiusToFahrenheit(100.0)\n      stdout.println(`100C = ${fahrenheit}F`)\n\n      grade <- classify(75)\n      stdout.println(`Grade: ${grade}`)","migrationContext":"EK9 uses named return declarations instead of a return keyword. Like Go's named returns but mandatory — the compiler enforces all paths initialise the return variable.","keywords":["declaration","function","no return keyword","return","rtn"],"primaryTopics":["function return pattern","<- rtn declaration"],"typicalErrors":[{"error":"E01072","correct":"    <- rtn as Float: radius * radius * 3.14159","incorrect":"      return radius * radius * 3.14159","explanation":"'return' does not exist in EK9. Declare the return variable with <- in the signature: '<- rtn as Type: expression'. The compiler ensures all paths initialise it."}],"companions":[],"oracleToolHint":{"tool":"ek9_scaffold","intent":"function","description":"Oracle can generate a function with the EK9 return value declaration pattern."}}
{"id":998,"category":"Functions and Methods","question":"Show me different function signatures in EK9 — no params, one param, multiple params, with return.","url":"https://ek9.io/qa/QA0998.html","alternatePhrasings":["What are the different ways to declare functions in EK9?","How do -> and <- work in EK9 function signatures?","Show me EK9 functions with various parameter and return combinations"],"answer":"EK9 functions use -> for parameters IN and <- for the return value OUT.\n\nNO PARAMS, NO RETURN:\n  greet()\n    stdout <- Stdout()\n    stdout.println(\"Hello\")\n\nNO PARAMS, WITH RETURN:\n  getVersion() as pure\n    <- rtn as String: \"1.0.0\"\n\nONE PARAM, WITH RETURN:\n  double() as pure\n    -> number as Integer\n    <- rtn as Integer: number * 2\n\nMULTIPLE PARAMS, WITH RETURN:\n  add() as pure\n    ->\n      left as Integer\n      right as Integer\n    <- rtn as Integer: left + right\n\nMultiple parameters go on separate lines under ->. Single parameters can be on the same line as ->.\n\nThe <- declares the return variable. The caller receives whatever that variable contains when the function completes. There is no return keyword.","ek9Example":"defines module qa.functionsandmethods.variedsignatures\n\n  defines function\n\n    //No params, no return\n    sayHello()\n      stdout <- Stdout()\n      stdout.println(\"Hello from EK9\")\n\n    //No params, with return\n    getVersion() as pure\n      <- rtn as String: \"1.0.0\"\n\n    //One param, with return\n    doubleValue() as pure\n      -> number as Integer\n      <- rtn as Integer: number * 2\n\n    //Multiple params, with return\n    addValues() as pure\n      ->\n        left as Integer\n        right as Integer\n      <- rtn as Integer: left + right\n\n    //Param and conditional return\n    absoluteValue() as pure\n      -> number as Integer\n      <- rtn as Integer: number\n\n      if number < 0\n        rtn: 0 - number\n\n  defines program\n\n    VariedSignaturesDemo()\n      stdout <- Stdout()\n\n      sayHello()\n      stdout.println(`Version: ${getVersion()}`)\n      stdout.println(`Double 21: ${doubleValue(21)}`)\n      stdout.println(`Add 15+27: ${addValues(15, 27)}`)\n      stdout.println(`Abs -42: ${absoluteValue(-42)}`)","migrationContext":"EK9 uses -> for input parameters and <- for return declarations. Multiple parameters go on separate lines under ->.","keywords":["arrow","function","parameter","pure","return","signature"],"primaryTopics":["function signatures","-> and <- in functions"],"typicalErrors":[{"error":"E01072","correct":"    <- rtn as Integer: left + right","incorrect":"      return left + right","explanation":"'return' does not exist in EK9. Use '<- rtn as Type: expression' to declare the return value in the function signature."}],"companions":[]}
{"id":999,"category":"Functions and Methods","question":"Write an EK9 function that takes a list of integers and returns the sum of all positive values.","url":"https://ek9.io/qa/QA0999.html","alternatePhrasings":["How do I write a function that processes a list and returns a result?","Show me an EK9 function that filters and accumulates values","Write a pure function that sums positive numbers from a list"],"answer":"Here is the function using a for loop with a guard:\n\nThe function declares <- rtn as Integer: 0 for the return value. It iterates the list, checks each number with a guard, and accumulates positive values with :=.\n\nAlternatively, use a stream pipeline with a for-range expression:\n  positiveSum <- for number in numbers\n    <- rtn <- 0\n    if number > 0\n      rtn := rtn + number\n\nBoth approaches produce the same result. The loop version is clearer for accumulation. The stream approach would use cat | filter | collect with a custom accumulator.","ek9Example":"defines module qa.functionsandmethods.writefromspec\n\n  defines function\n\n    sumPositives() as pure\n      -> numbers as List of Integer\n      <- rtn as Integer: 0\n\n      for number in numbers\n        if number > 0\n          rtn := rtn + number\n\n    isPositive() as pure\n      -> number as Integer\n      <- rtn as Boolean?\n\n      zeroThreshold <- 0\n      rtn: number > zeroThreshold\n\n  defines program\n\n    WriteFunctionDemo()\n      stdout <- Stdout()\n\n      mixedNumbers <- [10, -5, 23, -8, 15, -3, 42, -1]\n\n      positiveSum <- sumPositives(mixedNumbers)\n      stdout.println(`Sum of positives: ${positiveSum}`)\n\n      //Also show the positive numbers using a stream\n      stdout.println(\"Positive numbers:\")\n      cat mixedNumbers\n        | filter by isPositive\n        > stdout","migrationContext":"EK9 functions declare return values with <-, not return statements. For accumulation, use a loop with := to update the return variable.","keywords":["accumulate","filter","function","implement","sum","write"],"primaryTopics":["write function from specification","accumulation pattern"],"typicalErrors":[{"error":"E01072","correct":"      <- rtn as Integer: 0","incorrect":"      return 0","explanation":"'return' does not exist in EK9. Declare the return variable with '<- rtn as Integer: 0' and update it with ':=' as the function executes."}],"companions":[]}
{"id":1000,"category":"Getting Started","question":"How do I declare a variable in EK9?","url":"https://ek9.io/qa/QA1000.html","alternatePhrasings":["What is the syntax for creating a variable in EK9?","How do I create and initialise a variable?","Show me the basic variable declaration syntax"],"answer":"Use the <- operator to declare a variable. The compiler infers the type from the value.\n\n  greeting <- \"Hello\"        creates a String variable\n  count <- 0                 creates an Integer variable\n  price <- 19.99             creates a Float variable\n  active <- true             creates a Boolean variable\n  names <- List() of String  creates a List of String\n\nThe <- operator means 'create this variable with this value'. It can only be used once per variable name. After declaration, use := to update the value:\n\n  greeting <- \"Hello\"        declaration (first time)\n  greeting := \"Hi there\"     update (every time after)\n\nWith explicit type:\n  age as Integer: 30         declares 'age' as Integer with value 30\n  name as String: \"Steve\"    declares 'name' as String","ek9Example":"defines module qa.gettingstarted.declarefirstvariable\n\n  defines program\n\n    DeclareFirstVariable()\n      stdout <- Stdout()\n\n      // Declare variables with <- (type inferred)\n      greeting <- \"Hello, EK9\"\n      count <- 42\n      price <- 19.99\n      active <- true\n\n      stdout.println(greeting)\n      stdout.println(`Count: ${count}`)\n      stdout.println(`Price: ${price}`)\n      stdout.println(`Active: ${active}`)\n\n      // Update with := after declaration\n      greeting := \"Welcome to EK9\"\n      stdout.println(greeting)\n\n      // Declare with explicit type\n      language as String: \"EK9\"\n      stdout.println(language)","migrationContext":"EK9 uses <- for declaration with type inference. Unlike Java/Python where = does both declaration and assignment, EK9 separates them.","keywords":["arrow","basics","create","declare","first","variable"],"primaryTopics":["variable declaration","<- operator basics"],"typicalErrors":[{"error":"E50001","correct":"      greeting <- \"Hello, EK9\"","incorrect":"      greeting := \"Hello, EK9\"","explanation":"Use <- to declare a new variable; using := first means the variable was never declared, so it is unresolved and triggers E50001. See ek9 -h E50001 for details."}],"companions":[]}
{"id":1001,"category":"Functions and Methods","question":"How does a function return a value in EK9? There is no return keyword.","url":"https://ek9.io/qa/QA1001.html","alternatePhrasings":["Where does the return value come from if there is no return statement?","Explain the <- rtn pattern for returning values from functions","How does the compiler know what value to return from a function?"],"answer":"EK9 has no return keyword. Instead, you declare a return variable in the function signature with <-. The value of that variable when the function ends is what gets returned.\n\nTHE PATTERN:\n  functionName() as pure\n    -> inputParam as String           parameter coming IN\n    <- rtn as String: \"default\"       return value going OUT\n\nThe <- rtn as String declares a variable called 'rtn'. You can set it with an initial value (: \"default\") or leave it unset (: String()). The compiler ensures all execution paths give rtn a value.\n\nINSIDE THE FUNCTION:\nUpdate rtn using := or : to change what gets returned:\n  classify() as pure\n    -> score as Integer\n    <- rtn as String: \"fail\"\n\n    passingScore <- 50\n    if score >= passingScore\n      rtn: \"pass\"\n\nIf score >= 50, rtn becomes \"pass\". Otherwise rtn stays at its default \"fail\". Either way, the caller gets a String.\n\nTHE CALLER:\n  result <- classify(75)     result is \"pass\"\n  result2 <- classify(30)    result2 is \"fail\"","ek9Example":"defines module qa.functionsandmethods.returnsexplained\n\n  defines function\n\n    greetByName() as pure\n      -> personName as String\n      <- rtn as String: \"Hello, \" + personName\n\n    classifyScore() as pure\n      -> score as Integer\n      <- rtn as String: \"fail\"\n\n      passingScore <- 50\n      if score >= passingScore\n        rtn: \"pass\"\n\n    findLarger() as pure\n      ->\n        first as Integer\n        second as Integer\n      <- rtn as Integer: first\n\n      if second > first\n        rtn: second\n\n  defines program\n\n    ReturnsExplainedDemo()\n      stdout <- Stdout()\n\n      // Simple return — initial value is the return\n      message <- greetByName(\"Steve\")\n      stdout.println(message)\n\n      // Conditional return — rtn updated inside if\n      grade <- classifyScore(75)\n      stdout.println(`Score 75: ${grade}`)\n\n      grade2 <- classifyScore(30)\n      stdout.println(`Score 30: ${grade2}`)\n\n      // Two-path return\n      bigger <- findLarger(10, 25)\n      stdout.println(`Larger: ${bigger}`)","migrationContext":"EK9 uses named return declarations instead of return statements. The declared return variable is automatically returned when the function completes.","keywords":["declaration","function","no return","return","rtn","value"],"primaryTopics":["function return mechanism","no return keyword"],"typicalErrors":[{"error":"E01072","correct":"        rtn: \"pass\"","incorrect":"        return \"pass\"","explanation":"'return' does not exist in EK9; assign the declared return variable with 'rtn: value' instead of 'return value', which triggers E01072. See ek9 -h E01072 for details."}],"companions":[]}
{"id":1002,"category":"Streams and Pipelines","question":"Show me the simplest possible stream pipeline that outputs to stdout in EK9.","url":"https://ek9.io/qa/QA1002.html","alternatePhrasings":["What is the most basic stream pipeline in EK9?","How do I get started with EK9 streams?","Show me cat and > stdout in EK9"],"answer":"The simplest pipeline reads a collection and sends it to stdout:\n\n  cat items > stdout\n\nThat reads every item from 'items' and prints each to standard output. Like Unix: cat file > output.\n\nADD A FILTER:\n  cat items | filter by isValid > stdout\n\nKeeps only items where isValid returns true.\n\nADD A TRANSFORM:\n  cat items | map with transform > stdout\n\nApplies 'transform' to each item before output.\n\nCOLLECT INSTEAD OF OUTPUT:\n  results <- cat items | filter by isValid | collect as List of String\n\nGathers filtered items into a new List instead of printing them.\n\nThe | operator passes data from one stage to the next. The > operator sends the final result to a sink (stdout, a file, or any type with operator |).","ek9Example":"defines module qa.streams.firstpipeline\n\n  defines function\n\n    isLongEnough() as pure\n      -> word as String\n      <- rtn as Boolean?\n\n      minimumLength <- 3\n      rtn: word.length() > minimumLength\n\n  defines program\n\n    FirstPipelineDemo()\n      stdout <- Stdout()\n\n      names <- [\"Alice\", \"Bob\", \"Charlie\", \"Di\", \"Eve\", \"Frank\"]\n\n      // Simplest pipeline: cat > stdout\n      stdout.println(\"All names:\")\n      cat names > stdout\n\n      // With filter\n      stdout.println(\"Long names:\")\n      cat names | filter by isLongEnough > stdout\n\n      // Collect into a new list\n      longNames <- cat names\n        | filter by isLongEnough\n        | collect as List of String\n      stdout.println(`Collected: ${longNames}`)","migrationContext":"EK9 streams use Unix pipe syntax: cat source | operation | operation > sink. The > terminal replaces forEach/println loops.","keywords":["basic","beginner","cat","first","pipeline","stdout","stream"],"primaryTopics":["first stream pipeline","cat and > stdout"],"typicalErrors":[{"error":"E50001","correct":"      cat names > stdout","incorrect":"      names.forEach(stdout::println)","explanation":"EK9 streams use pipe syntax, not method chaining. Write 'cat collection > stdout' to print all items."}],"companions":[]}
{"id":1003,"category":"Operators and Expressions","question":"What method does each EK9 operator call internally?","url":"https://ek9.io/qa/QA1003.html","alternatePhrasings":["What is the mapping between EK9 operators and their method names?","What does each EK9 operator symbol translate to?","Show me the operator to method name mapping in EK9"],"answer":"Every EK9 operator calls a specific method on the object. Here are the key mappings:\n\nCONVERSION OPERATORS:\n  $variable      calls _string()    returns String\n  #? variable    calls _hashcode()  returns Integer\n  #^ variable    calls _promote()   returns a different type\n  $$ variable    calls _json()      returns JSON String\n\nCHECKING OPERATORS:\n  variable?      calls _isSet()     returns Boolean (suffix, no parentheses)\n\nCOMPARISON OPERATORS:\n  a == b         calls _eq()        returns Boolean\n  a <> b         calls _neq()       returns Boolean\n  a <=> b        calls _cmp()       returns Integer (-1, 0, 1)\n\nCOPY/MERGE OPERATORS:\n  a :=: b        calls _copy()      copies b into a\n  a :~: b        calls _merge()     merges b into a\n  a :^: b        calls _replace()   replaces a content with b\n\nThe 'default operator' keyword auto-generates all of these from your declared fields. You only need to implement them manually for custom behaviour.","ek9Example":"defines module qa.operators.methodnames\n\n  defines record\n\n    Sensor\n      location as String: String()\n      reading as Float: 0.0\n\n      Sensor()\n        ->\n          location as String\n          reading as Float\n        this.location :=: location\n        this.reading :=: reading\n\n      default operator\n\n  defines program\n\n    OperatorMethodNamesDemo()\n      stdout <- Stdout()\n\n      sensor <- Sensor(\"Kitchen\", 22.5)\n\n      // $ calls _string()\n      sensorText <- $sensor\n      stdout.println(`String: ${sensorText}`)\n\n      // #? calls _hashcode()\n      sensorHash <- #? sensor\n      stdout.println(`Hash: ${sensorHash}`)\n\n      // ? calls _isSet() — suffix, no parentheses\n      stdout.println(`Is set: ${sensor?}`)\n\n      // <=> calls _cmp()\n      other <- Sensor(\"Lounge\", 20.0)\n      comparison <- sensor <=> other\n      stdout.println(`Compare: ${comparison}`)","migrationContext":"EK9 operators are method calls with fixed names. $ is _string() (like Java toString), #? is _hashcode() (like Java hashCode), ? is _isSet() (like Rust is_some).","keywords":["hashcode","mapping","method","name","operator","promote","string"],"primaryTopics":["operator method names","operator to method mapping"],"typicalErrors":[{"error":"E50060","correct":"      stdout.println(`Is set: ${sensor?}`)","incorrect":"      stdout.println(`Is set: ${sensor.toString()}`)","explanation":"EK9 objects have no toString() method — the $ prefix operator calls _string(); use $variable (or ${...} interpolation) for String conversion. See ek9 -h E50060 for details."}],"companions":[]}
{"id":1004,"category":"Getting Started","question":"In Rust I check if an Option has a value with is_some(). How do I do this in EK9?","url":"https://ek9.io/qa/QA1004.html","alternatePhrasings":["What is the EK9 equivalent of Rust's Option.is_some()?","I use Rust's if let Some(v) pattern. What does EK9 use?","How do I handle optional values coming from Rust to EK9?"],"answer":"In EK9, append ? after the variable name. It returns true if the value is set.\n\nRust: if my_option.is_some() { use(my_option.unwrap()) }\nEK9:  if myValue?\n        use(myValue)\n\nFor Rust's 'if let Some(v) = expression' pattern, EK9 uses a guard:\nRust: if let Some(name) = find_user(id) { println!(\"{}\", name); }\nEK9:  if name <- findUser(id)\n        stdout.println(name)\n\nThe guard declares 'name' AND checks if findUser returned a set value. The block only runs if set — exactly like Rust's if let.\n\nEK9 has no None/null/nil. Values are either absent, unset, or set. The ? suffix checks for set.","ek9Example":"defines module qa.gettingstarted.fromrustoption\n\n  defines function\n\n    findUser() as pure\n      -> userId as Integer\n      <- rtn as String: String()\n\n      knownId <- 42\n      if userId == knownId\n        rtn: \"Alice\"\n\n  defines program\n\n    FromRustOptionDemo()\n      stdout <- Stdout()\n\n      // Like Rust: if let Some(name) = find_user(42)\n      if name <- findUser(42)\n        stdout.println(`Found: ${name}`)\n\n      // Like Rust: if option.is_some()\n      maybeUser <- findUser(99)\n      if maybeUser?\n        stdout.println(maybeUser)\n      else\n        stdout.println(\"Not found\")","migrationContext":"Rust developers: ? suffix replaces is_some(), guard replaces if let Some(). No unwrap() needed — the guard guarantees the value is set inside the block.","keywords":["guard","isSet","migration","none","option","rust","some"],"primaryTopics":["Rust Option to EK9","is_some to ? suffix"],"typicalErrors":[{"error":"E01073","correct":"if maybeUser?","incorrect":"if maybeUser <> null","explanation":"'null' does not exist in EK9; use the tri-state '?' isSet operator (or an 'if name <- expr()' guard) instead. See ek9 -h E01073 for details."}],"companions":[]}
{"id":1005,"category":"Getting Started","question":"In Go I use := for short variable declaration and = for assignment. How does EK9 handle this?","url":"https://ek9.io/qa/QA1005.html","alternatePhrasings":["What is the EK9 equivalent of Go's := and = operators?","I'm a Go developer. How do declaration and assignment work in EK9?","How does EK9 separate declaration from assignment like Go does?"],"answer":"EK9 separates declaration from assignment just like Go, but with different symbols.\n\nGo:  name := \"Steve\"     short declaration (creates variable)\nEK9: name <- \"Steve\"     declaration (creates variable)\n\nGo:  name = \"Alice\"      assignment (updates variable)\nEK9: name := \"Alice\"     assignment (updates variable)\n\nNotice the swap: Go's := (declare) maps to EK9's <- (declare). Go's = (assign) maps to EK9's := (assign).\n\nEK9 also has : and = as aliases for :=\n  name := \"Alice\"     assignment\n  name: \"Alice\"       same thing\n  name = \"Alice\"      same thing\n\nAnd :=? for guarded assignment (like Go's 'if err != nil' pattern):\n  fallback :=? \"default\"     only assigns if fallback is currently unset\n\nGo's 'if err := f(); err != nil' pattern becomes an EK9 guard:\n  if result <- doSomething()\n    useResult(result)","ek9Example":"defines module qa.gettingstarted.fromgodeclaration\n\n  defines program\n\n    FromGoDeclarationDemo()\n      stdout <- Stdout()\n\n      // Like Go: name := \"Steve\" (short declaration)\n      name <- \"Steve\"\n      stdout.println(name)\n\n      // Like Go: name = \"Alice\" (assignment)\n      name := \"Alice\"\n      stdout.println(name)\n\n      // Like Go: if err := f(); err != nil { ... }\n      if greeting <- greetUser(name)\n        stdout.println(greeting)\n\n  defines function\n\n    greetUser() as pure\n      -> userName as String\n      <- rtn as String: \"Welcome, \" + userName","migrationContext":"Go developers: your := becomes EK9's <-, your = becomes EK9's :=. The separation of declaration from assignment is the same concept, different symbols.","keywords":["assignment","declaration","go","golang","migration","short variable"],"primaryTopics":["Go := to EK9 <-","Go = to EK9 :="],"typicalErrors":[{"error":"E50001","correct":"      name <- \"Steve\"\n      stdout.println(name)","incorrect":"      name := \"Steve\"\n      name = \"Alice\"","explanation":"In EK9, <- declares (like Go's :=) and := assigns (like Go's =). Using := before <- means the variable does not exist yet."}],"companions":[]}
{"id":1006,"category":"Getting Started","question":"I'm a Python developer. What are the key differences I need to know for EK9?","url":"https://ek9.io/qa/QA1006.html","alternatePhrasings":["How do I transition from Python to EK9?","What will surprise a Python developer about EK9?","Python to EK9 — what changes?"],"answer":"Key differences for Python developers:\n\nDECLARATION: Python 'x = 5' does both. EK9 separates: 'x <- 5' (create), 'x := 5' (update).\n\nRETURN: Python 'return value'. EK9 has no return — declare '<- rtn as Type' in the signature.\n\nNONE/NULL: Python 'if x is not None'. EK9 has no None — use 'if x?' (suffix ?) or guard 'if x <- expr()'.\n\nLIST COMPREHENSION: Python '[x*2 for x in items if x > 0]'. EK9 uses stream pipeline: 'cat items | filter by isPositive | map with doubleIt | collect as List of Integer'.\n\nINDENTATION: Both use indentation. EK9 uses 2 spaces per level.\n\nTYPING: Python is dynamic. EK9 is statically typed with type inference (x <- 5 infers Integer).\n\nBREAK/CONTINUE: Python has them. EK9 does not — use stream pipelines with head (replaces break) and filter (replaces continue).\n\nWALRUS :=  Python's := is an expression (assigns AND evaluates). EK9's := is a statement (assigns only, cannot use in conditions). Use <- guard for Python's walrus pattern.","ek9Example":"defines module qa.gettingstarted.frompython\n\n  defines function\n\n    doubleValue() as pure\n      -> number as Integer\n      <- rtn as Integer: number * 2\n\n    isPositive() as pure\n      -> number as Integer\n      <- rtn as Boolean?\n\n      zeroThreshold <- 0\n      rtn: number > zeroThreshold\n\n  defines program\n\n    FromPythonDemo()\n      stdout <- Stdout()\n\n      // Python: x = 5 → EK9: x <- 5 (declare), x := 10 (update)\n      score <- 5\n      score := 10\n      stdout.println(`Score: ${score}`)\n\n      // Python: return x*2 → EK9: <- rtn as Integer: number * 2\n      doubled <- doubleValue(21)\n      stdout.println(`Doubled: ${doubled}`)\n\n      // Python: [x*2 for x in items if x > 0] → EK9: stream pipeline\n      numbers <- [-3, 5, -1, 8, 2, -4, 7]\n      positiveDoubled <- cat numbers\n        | filter by isPositive\n        | map with doubleValue\n        | collect as List of Integer\n      stdout.println(`Result: ${positiveDoubled}`)","migrationContext":"Python developers: EK9 uses indentation like Python but is statically typed. The biggest adjustment is no return statement and no None.","keywords":["comprehension","differences","migration","none","python","return","walrus"],"primaryTopics":["Python to EK9 migration","key differences for Python developers"],"typicalErrors":[{"error":"E01072","correct":"    <- rtn as Integer: number * 2","incorrect":"      return number * 2","explanation":"'return' does not exist in EK9. Declare the return variable with '<- rtn as Type: expression' in the function signature."}],"companions":[]}
{"id":1007,"category":"Getting Started","question":"I'm a Kotlin developer. What are the similarities and differences with EK9?","url":"https://ek9.io/qa/QA1007.html","alternatePhrasings":["How does EK9 compare to Kotlin?","What will feel familiar to a Kotlin developer in EK9?","Kotlin to EK9 — what's the same and what's different?"],"answer":"EK9 and Kotlin share several design decisions:\n\nSAME: CLOSED BY DEFAULT\nKotlin classes are final by default (use 'open' to extend). EK9 classes are closed by default (use 'as open' to extend). Same concept, slightly different keyword.\n\nSAME: NULL SAFETY\nKotlin has nullable types with ?. and ?: operators. EK9 has tri-state with ? suffix and ?? coalescing. Both prevent null pointer exceptions at compile time.\n\nSAME: DATA CLASSES / RECORDS\nKotlin 'data class' auto-generates equals/hashCode/toString/copy. EK9 'defines record' with 'default operator' auto-generates the same set of operators.\n\nDIFFERENT: NO RETURN\nKotlin has return. EK9 declares return variable with <- in the signature. No return keyword.\n\nDIFFERENT: STREAM SYNTAX\nKotlin uses method chaining: list.filter { }.map { }.toList(). EK9 uses pipe syntax: cat list | filter by fn | map with fn | collect as List of T.\n\nDIFFERENT: DECLARATION\nKotlin uses val/var keywords. EK9 uses <- operator for declaration, := for reassignment.\n\nDIFFERENT: SWITCH\nKotlin 'when' is an expression. EK9 'switch' is also an expression — similar concept.","ek9Example":"defines module qa.gettingstarted.fromkotlin\n\n  defines record\n\n    //Like Kotlin: data class Coordinate(val latitude: Float, val longitude: Float)\n    Coordinate\n      latitude as Float: 0.0\n      longitude as Float: 0.0\n\n      Coordinate()\n        ->\n          latitude as Float\n          longitude as Float\n        this.latitude :=: latitude\n        this.longitude :=: longitude\n\n      //Like Kotlin's auto-generated equals/hashCode/toString/copy\n      default operator\n\n  defines program\n\n    FromKotlinDemo()\n      stdout <- Stdout()\n\n      //Like Kotlin: val point = Coordinate(51.5, -0.1)\n      point <- Coordinate(51.5, -0.1)\n      stdout.println($point)\n\n      //Like Kotlin: if (value != null) — EK9 uses ? suffix\n      if point?\n        stdout.println(\"Point is set\")\n\n      //Like Kotlin: val other = point.copy() — EK9 uses :=: operator\n      other <- Coordinate(0.0, 0.0)\n      other :=: point\n      stdout.println($other)","migrationContext":"Kotlin developers will find EK9 familiar: closed types, null safety, data classes. The main adjustment is no return keyword and pipe syntax for streams.","keywords":["closed","data class","kotlin","migration","null safety","record","when"],"primaryTopics":["Kotlin to EK9 migration","Kotlin developer guide"],"typicalErrors":[],"companions":[]}
{"id":1008,"category":"Collections and Data Structures","question":"In Rust I use Vec<T> for dynamic arrays. What is the EK9 equivalent?","url":"https://ek9.io/qa/QA1008.html","alternatePhrasings":["What is the EK9 equivalent of Rust's Vec?","How do I create and use a typed list in EK9?","How does EK9's List compare to Rust's Vec?"],"answer":"EK9 uses List of T for dynamic collections.\n\nRust: let mut items: Vec<String> = Vec::new();\nEK9:  items <- List() of String\n\nRust: items.push(\"hello\");\nEK9:  items += \"hello\"\n\nRust: items.len()\nEK9:  items.length()\n\nRust: for item in &items { println!(\"{}\", item); }\nEK9:  cat items > stdout\n\nRust: items.iter().filter(|x| x.len() > 3).collect::<Vec<_>>()\nEK9:  cat items | filter by isLongEnough | collect as List of String\n\nLIST LITERAL SYNTAX:\nRust: let scores = vec![10, 20, 30];\nEK9:  scores <- [10, 20, 30]\n\nEK9 lists are always SET when created — even an empty list is set (not unset). This differs from Rust's Option<Vec<T>> pattern.","ek9Example":"defines module qa.collections.fromrustvec\n\n  defines function\n\n    isLongEnough() as pure\n      -> word as String\n      <- rtn as Boolean?\n\n      minimumLength <- 3\n      rtn: word.length() > minimumLength\n\n  defines program\n\n    FromRustVecDemo()\n      stdout <- Stdout()\n\n      // Like Rust: let mut items = vec![\"hello\", \"world\", \"hi\"]\n      items <- [\"hello\", \"world\", \"hi\", \"there\", \"ok\"]\n\n      // Like Rust: items.len()\n      stdout.println(`Length: ${items.length()}`)\n\n      // Like Rust: for item in &items { println!(\"{}\", item); }\n      stdout.println(\"All items:\")\n      cat items > stdout\n\n      // Like Rust: items.iter().filter(|x| x.len() > 3).collect()\n      longItems <- cat items\n        | filter by isLongEnough\n        | collect as List of String\n      stdout.println(`Long items: ${longItems}`)","migrationContext":"Rust developers: Vec<T> maps to List of T. push() maps to +=. iter().filter().collect() maps to cat | filter | collect.","keywords":["collection","generic","list","migration","rust","vec"],"primaryTopics":["Rust Vec to EK9 List","collection migration"],"typicalErrors":[],"companions":[]}
{"id":1009,"category":"Error Handling and Exceptions","question":"In Go I return (value, error) and check if err != nil. What does EK9 use instead?","url":"https://ek9.io/qa/QA1009.html","alternatePhrasings":["What is the EK9 equivalent of Go's error return pattern?","How do I handle errors in EK9 coming from Go?","Go error handling vs EK9 — what's the difference?"],"answer":"EK9 has two approaches that replace Go's (value, error) pattern:\n\n1. GUARD EXPRESSIONS (most common)\nGo:  result, err := doSomething(); if err != nil { handleError(err) }\nEK9: if result <- doSomething()\n       useResult(result)\n     else\n       handleMissing()\n\nThe guard declares 'result' AND checks if the function returned a set value. The else block handles the missing/error case.\n\n2. RESULT TYPE (for explicit ok/error)\nGo:  func divide(a, b float64) (float64, error)\nEK9: divide() as pure\n       -> a as Float, b as Float\n       <- rtn as Result of (Float, String)\n\nResult holds either an ok value, an error value, or both.\n\n3. TRY/CATCH (for exceptions)\nGo:  panic/recover (rare). EK9: try/catch/finally (like Java).\n\nThe guard pattern is the closest to Go's style — short, inline, and the variable only exists if the operation succeeded.","ek9Example":"defines module qa.errorhandling.fromgoerror\n\n  defines function\n\n    safeDivide() as pure\n      ->\n        numerator as Float\n        denominator as Float\n      <- rtn as Float: Float()\n\n      if denominator?\n        rtn: numerator / denominator\n\n  defines program\n\n    FromGoErrorDemo()\n      stdout <- Stdout()\n\n      // Like Go: result, err := safeDivide(10, 3); if err == nil { use(result) }\n      if answer <- safeDivide(10.0, 3.0)\n        stdout.println(`Result: ${answer}`)\n\n      // Like Go: if err != nil { handleError() }\n      if noAnswer <- safeDivide(10.0, 0.0)\n        stdout.println(`Got: ${noAnswer}`)\n      else\n        stdout.println(\"Division returned unset (like Go's err != nil)\")\n\n      // With ?? default (like Go's 'or default' pattern)\n      safeAnswer <- safeDivide(10.0, 0.0) ?? 0.0\n      stdout.println(`Safe default: ${safeAnswer}`)","migrationContext":"Go developers: your 'if err != nil' pattern becomes EK9's 'if result <- expr()' guard. The guard checks isSet, not nil (EK9 has no nil).","keywords":["error","go","guard","migration","nil","result"],"primaryTopics":["Go error handling to EK9","error return to guard"],"typicalErrors":[{"error":"E01073","correct":"      <- rtn as Float: Float()","incorrect":"      <- rtn as Float: null","explanation":"'null' (and Go's 'nil') do not exist in EK9 — return the tri-state unset value Float() instead of a null literal. See ek9 -h E01073 for details."}],"companions":[]}
{"id":1010,"category":"Collections and Data Structures","question":"In Python I use dict for key-value pairs. How do dictionaries work in EK9?","url":"https://ek9.io/qa/QA1010.html","alternatePhrasings":["What is the EK9 equivalent of Python's dict?","How do I create and use a Dict in EK9?","How does EK9's Dict compare to Python's dictionary?"],"answer":"EK9 uses Dict of (KeyType, ValueType) for key-value pairs.\n\nPython: scores = {'Alice': 95, 'Bob': 82}\nEK9:   scores <- {\"Alice\": 95, \"Bob\": 82}\n\nPython: scores['Charlie'] = 78\nEK9:   scores += DictEntry(\"Charlie\", 78)\n\nPython: if 'Alice' in scores: print(scores['Alice'])\nEK9:   aliceScore <- scores.getOrDefault(\"Alice\", 0)\n       stdout.println($aliceScore)\n\nPython: for key, value in scores.items(): print(f'{key}: {value}')\nEK9:   cat scores > stdout\n\nPython: len(scores)\nEK9:   scores.length()\n\nLITERAL SYNTAX:\nEK9 supports dict literals with {key: value} syntax, just like Python.\n\nACCESS PATTERN:\nPython raises KeyError on missing keys. EK9 uses getOrDefault(key, default) for safe access.","ek9Example":"defines module qa.collections.frompythondict\n\n  defines program\n\n    FromPythonDictDemo()\n      stdout <- Stdout()\n\n      // Like Python: scores = {'Alice': 95, 'Bob': 82}\n      scores <- {\"Alice\": 95, \"Bob\": 82}\n\n      // Like Python: for key, value in scores.items(): print(...)\n      stdout.println(\"All scores:\")\n      cat scores > stdout\n\n      // Like Python: scores.get('Alice', 0)\n      aliceScore <- scores.getOrDefault(\"Alice\", 0)\n      stdout.println(`Alice's score: ${aliceScore}`)\n\n      // Like Python: scores.get('Charlie', 0) — EK9 uses getOrDefault\n      charlieScore <- scores.getOrDefault(\"Charlie\", 0)\n      stdout.println(`Charlie's score (default 0): ${charlieScore}`)\n\n      // Like Python: len(scores)\n      stdout.println(`Count: ${scores.length()}`)","migrationContext":"Python developers: dict maps to Dict of (K, V). Use {key: value} literals. Access with .get() and guard, not [] indexing.","keywords":["collection","dict","dictionary","key value","migration","python"],"primaryTopics":["Python dict to EK9 Dict","dictionary migration"],"typicalErrors":[{"error":"E50001","correct":"      aliceScore <- scores.getOrDefault(\"Alice\", 0)","incorrect":"      aliceScore <- scores[\"Alice\"]","explanation":"EK9 has no [] indexing operator. Use getOrDefault(key, default) to safely access Dict values."}],"companions":[]}
{"id":1011,"category":"Getting Started","question":"In Python I use 'with psycopg2.connect() as conn:' for database connections. How do I manage resources in EK9?","url":"https://ek9.io/qa/QA1011.html","alternatePhrasings":["What is the EK9 equivalent of Python's context manager for connections?","How do I safely open and close a connection in EK9?","How does EK9 handle resource cleanup like Python's with statement?"],"answer":"EK9 uses try-with-resource for automatic cleanup, similar to Python's 'with' statement.\n\nPython:\n  with psycopg2.connect(connString) as conn:\n      cursor = conn.cursor()\n      cursor.execute('SELECT ...')\n      results = cursor.fetchall()\n  # conn automatically closed here\n\nEK9:\n  try\n    -> connection <- openConnection(connString)\n    results <- queryDatabase(connection, sqlQuery)\n    processResults(results)\n  catch\n    -> ex as Exception\n    stderr.println(`Database error: ${$ex}`)\n  finally\n    cleanup()\n\nThe try -> resource <- expr() pattern declares a resource that is automatically closed when the try block ends. The -> means 'data flowing in' — the connection is the resource being managed.\n\nIf the resource implements close(), EK9 calls it automatically — like Python's __exit__() or Java's AutoCloseable.","ek9Example":"defines module qa.gettingstarted.frompythonconnection\n\n  defines function\n\n    buildGreeting() as pure\n      -> personName as String\n      <- rtn as String: \"Hello, \" + personName\n\n  defines program\n\n    ConnectionPatternDemo()\n      stdout <- Stdout()\n      stderr <- Stderr()\n\n      // Pattern: try-with-resource for connection-like objects\n      // In Python: with open('file.txt') as f: data = f.read()\n      // In EK9: try -> resource <- openResource() ... catch/finally\n\n      try\n        greeting <- buildGreeting(\"Database User\")\n        stdout.println(greeting)\n      catch\n        -> ex as Exception\n        stderr.println(`Error: ${ex}`)\n      finally\n        stdout.println(\"Cleanup complete\")","migrationContext":"Python developers: your 'with X as y:' pattern becomes EK9's 'try -> y <- X'. The finally block replaces __exit__. Exception handling uses catch -> ex as Exception.","keywords":["cleanup","connection","migration","python","resource","try","with"],"primaryTopics":["Python context manager to EK9 try-with-resource"],"typicalErrors":[{"error":"E01073","correct":"      try\n        greeting <- buildGreeting(\"Database User\")\n        stdout.println(greeting)","incorrect":"      connection <- openConnection(connString)\n      if connection != null\n        processData(connection)","explanation":"'null' does not exist in EK9. Use try-with-resource for connections: 'try -> connection <- expr()'. See ek9 -h E01073."}],"companions":[]}
{"id":1012,"category":"Getting Started","question":"In Python I iterate database rows with 'for row in cursor'. How do I process collections of records in EK9?","url":"https://ek9.io/qa/QA1012.html","alternatePhrasings":["How do I iterate over results and transform them in EK9?","What is the EK9 equivalent of Python's for loop with transformation?","How do I process a list of records in EK9?"],"answer":"EK9 provides two approaches: for-in loops and stream pipelines.\n\nPython:\n  for row in cursor:\n      if row['active']:\n          print(f\"{row['name']}: {row['score']}\")\n\nEK9 LOOP APPROACH:\n  for entry in records\n    if entry.active?\n      stdout.println(`${entry.personName}: ${$entry.score}`)\n\nEK9 STREAM APPROACH:\n  cat records | filter by isActive > stdout\n\nThe stream approach is preferred when you are filtering, transforming, or collecting. The loop approach is fine for simple iteration with side effects.\n\nFor transforming results into a new collection:\nPython: names = [row['name'] for row in cursor if row['active']]\nEK9:    names <- cat records | filter by isActive | map with extractName | collect as List of String","ek9Example":"defines module qa.gettingstarted.frompythoniteration\n\n  defines record\n\n    UserRecord\n      personName as String: String()\n      score as Integer: 0\n      active as Boolean: false\n\n      UserRecord()\n        ->\n          personName as String\n          score as Integer\n          active as Boolean\n        this.personName :=: personName\n        this.score :=: score\n        this.active :=: active\n\n      default operator\n\n  defines function\n\n    isActive() as pure\n      -> entry as UserRecord\n      <- rtn as Boolean: entry.active\n\n    extractName() as pure\n      -> entry as UserRecord\n      <- rtn as String: entry.personName\n\n  defines program\n\n    IterationDemo()\n      stdout <- Stdout()\n\n      records <- [\n        UserRecord(\"Alice\", 95, true),\n        UserRecord(\"Bob\", 42, false),\n        UserRecord(\"Charlie\", 88, true),\n        UserRecord(\"Diana\", 73, false),\n        UserRecord(\"Eve\", 91, true)\n      ]\n\n      // Stream approach: filter active, print\n      stdout.println(\"Active users:\")\n      cat records | filter by isActive > stdout\n\n      // Stream approach: filter, transform, collect\n      activeNames <- cat records\n        | filter by isActive\n        | map with extractName\n        | collect as List of String\n      stdout.println(`Active names: ${activeNames}`)","migrationContext":"Python developers: for-in loops work similarly. For filter/map/collect patterns, use stream pipelines instead of list comprehensions.","keywords":["cursor","for","iteration","migration","python","stream","transform"],"primaryTopics":["Python iteration to EK9 streams","for loop vs stream pipeline"],"typicalErrors":[{"error":"E01081","correct":"      activeNames <- cat records\n        | filter by isActive\n        | map with extractName\n        | collect as List of String","incorrect":"      activeNames <- [entry.personName for entry in records if entry.active]","explanation":"EK9 has no list-comprehension syntax — use a stream pipeline: cat source | filter by fn | map with fn | collect as Type. See ek9 -h E01081 for details."}],"companions":[]}
{"id":1013,"category":"Getting Started","question":"In Python I build SQL queries with f-strings. How do I build strings safely in EK9?","url":"https://ek9.io/qa/QA1013.html","alternatePhrasings":["How does EK9 string interpolation compare to Python f-strings?","What is the EK9 equivalent of Python's f'Hello {name}'?","How do I safely build parameterised strings in EK9?"],"answer":"EK9 uses backtick strings with ${expression} for interpolation, similar to Python f-strings.\n\nPython: f\"Hello {name}, you have {count} items\"\nEK9:   `Hello ${name}, you have ${$count} items`\n\nKEY DIFFERENCES:\n- EK9 uses backticks ` not quotes with f prefix\n- EK9 uses ${expression} not {expression}\n- For non-String types, use $variable inside ${} to convert: ${$count} converts Integer to String\n- Double-quoted strings \"...\" are plain text — no interpolation\n\nSAFE STRING BUILDING:\nPython: f\"SELECT * FROM users WHERE id = {user_id}\"  (UNSAFE — SQL injection)\nEK9 equivalent would also be unsafe if done this way.\n\nFor safe parameterised strings, build them with named variables:\n  tableName <- \"users\"\n  columnName <- \"id\"\n  query <- `SELECT * FROM ${tableName} WHERE ${columnName} = ?`\n\nEK9's sanitized parameters can prevent injection at compile time for web services.","ek9Example":"defines module qa.gettingstarted.frompythonstringbuilding\n\n  defines program\n\n    StringBuildingDemo()\n      stdout <- Stdout()\n\n      // Like Python: f\"Hello {name}\"\n      userName <- \"Alice\"\n      greeting <- `Hello ${userName}`\n      stdout.println(greeting)\n\n      // Like Python: f\"Score: {score}\" (non-string needs conversion)\n      score <- 95\n      scoreLine <- `Score: ${score}`\n      stdout.println(scoreLine)\n\n      // Like Python: f\"{first} {last} ({age})\"\n      firstName <- \"Bob\"\n      lastName <- \"Smith\"\n      age <- 30\n      fullDescription <- `${firstName} ${lastName} (${age})`\n      stdout.println(fullDescription)\n\n      // Double-quoted strings are plain — no interpolation\n      plainText <- \"This ${is} not interpolated\"\n      stdout.println(plainText)","migrationContext":"Python developers: f-strings map to EK9 backtick strings. Use ${expression} instead of {expression}. Double-quoted strings have no interpolation.","keywords":["backtick","fstring","interpolation","migration","python","safe","string"],"primaryTopics":["Python f-strings to EK9 backticks","string interpolation"],"typicalErrors":[],"companions":[]}
{"id":1014,"category":"Error Handling and Exceptions","question":"In Python I use try/except for error handling. How does EK9's try/catch compare?","url":"https://ek9.io/qa/QA1014.html","alternatePhrasings":["What is the EK9 equivalent of Python's try/except/finally?","How do I catch exceptions in EK9 coming from Python?","How does EK9 exception handling differ from Python?"],"answer":"EK9 uses try/catch/finally, similar to Python's try/except/finally but with different syntax.\n\nPython:\n  try:\n      result = risky_operation()\n  except ValueError as e:\n      print(f'Error: {e}')\n  finally:\n      cleanup()\n\nEK9:\n  try\n    result <- riskyOperation()\n  catch\n    -> ex as Exception\n    stdout.println(`Error: ${$ex}`)\n  finally\n    cleanup()\n\nKEY DIFFERENCES:\n- catch uses -> for the exception parameter (data flowing IN to the handler)\n- EK9 has a single Exception type (no ValueError, TypeError, etc.)\n- No multiple except/catch blocks for different types\n- The exception variable uses -> on its own line, like function parameters\n\nTRY AS EXPRESSION:\nPython has no try-expression. EK9 does:\n  safeResult <- try\n    riskyOperation()\n  catch\n    -> ex as Exception\n    defaultValue()","ek9Example":"defines module qa.errorhandling.frompythonexception\n\n  defines function\n\n    riskyDivision() as pure\n      ->\n        numerator as Float\n        denominator as Float\n      <- rtn as Float: Float()\n\n      if denominator?\n        rtn: numerator / denominator\n\n  defines program\n\n    ExceptionDemo()\n      stdout <- Stdout()\n      stderr <- Stderr()\n\n      // Like Python: try/except/finally\n      try\n        answer <- riskyDivision(10.0, 3.0)\n        if answer?\n          stdout.println(`Result: ${answer}`)\n      catch\n        -> ex as Exception\n        stderr.println(`Caught: ${ex}`)\n      finally\n        stdout.println(\"Done\")\n\n      // Guard approach (simpler for functions that return unset)\n      if safeAnswer <- riskyDivision(10.0, 0.0)\n        stdout.println(`Got: ${safeAnswer}`)\n      else\n        stdout.println(\"Operation returned unset\")","migrationContext":"Python developers: try/except becomes try/catch. The catch uses '-> ex as Exception' on its own line. EK9 has one Exception type, not a hierarchy.","keywords":["catch","except","exception","finally","migration","python","try"],"primaryTopics":["Python try/except to EK9 try/catch"],"typicalErrors":[{"error":"E01010","correct":"      catch\n        -> ex as Exception\n        stderr.println(`Caught: ${ex}`)","incorrect":"      except Exception as ex:\n        stderr.println(`Error: ${$ex}`)","explanation":"EK9 uses 'catch' not 'except'. The exception parameter uses '-> ex as Exception' on its own line, like function parameters."}],"companions":[]}
{"id":1015,"category":"Security and Sanitization","question":"In Python I use parameterised queries to prevent SQL injection. How does EK9 handle input sanitisation?","url":"https://ek9.io/qa/QA1015.html","alternatePhrasings":["How do I prevent injection attacks in EK9?","What is the EK9 equivalent of SQL parameterised queries?","How does EK9's sanitized keyword work for safe input handling?"],"answer":"EK9 enforces input sanitisation at COMPILE TIME with the 'sanitized' keyword. This is unique — no other language does this.\n\nPython relies on runtime discipline:\n  cursor.execute('SELECT * FROM users WHERE name = %s', (user_input,))  # Safe\n  cursor.execute(f'SELECT * FROM users WHERE name = {user_input}')       # UNSAFE\n\nEK9 makes unsafe code a COMPILE ERROR:\n  processQuery()\n    -> userInput as sanitized String\n    <- rtn as String: `SELECT * FROM users WHERE name = ${userInput}`\n\nThe 'sanitized' keyword tells the compiler this parameter comes from external input. The compiler then tracks it through all operations and rejects any pattern that could allow injection.\n\nYou cannot pass a raw unsanitized String where a sanitized String is expected — the compiler rejects it. This prevents the entire class of injection vulnerabilities at compile time rather than relying on developer discipline.\n\nSanitized parameters work on functions, methods, and service endpoints.","ek9Example":"defines module qa.security.frompythonsqlsafety\n\n  defines function\n\n    buildSafeGreeting() as pure\n      -> userName as String\n      <- rtn as String: `Hello, ${userName}`\n\n  defines program\n\n    SqlSafetyDemo()\n      stdout <- Stdout()\n\n      // Safe: using known values\n      trustedName <- \"Alice\"\n      greeting <- buildSafeGreeting(trustedName)\n      stdout.println(greeting)\n\n      // In a real service, parameters from HTTP requests would be 'sanitized':\n      // processRequest()\n      //   -> queryParam as sanitized String\n      //   <- rtn as HTTPResponse: ...\n      // The compiler tracks sanitized values and rejects unsafe operations\n\n      stdout.println(\"EK9 enforces input safety at compile time\")","migrationContext":"Python developers: your parameterised queries discipline becomes compile-time enforcement in EK9. The 'sanitized' keyword makes injection impossible, not just unlikely.","keywords":["compile time","injection","migration","python","sanitized","security","sql"],"primaryTopics":["sanitized keyword","compile-time injection prevention"],"typicalErrors":[],"companions":[]}
{"id":1016,"category":"Security and Sanitization","question":"In Python I load database passwords from environment variables. How does EK9 handle credentials and secrets?","url":"https://ek9.io/qa/QA1016.html","alternatePhrasings":["How do I safely handle passwords and API keys in EK9?","What is the EK9 approach to managing secrets and credentials?","How does EK9 prevent accidental credential exposure?"],"answer":"EK9 has the Sensitive type for secrets. Values wrapped in Sensitive are automatically redacted in logs, string output, and error messages.\n\nPython:\n  password = os.environ['DB_PASSWORD']    # Just a plain string — could be logged accidentally\n  print(f'Connecting with {password}')     # OOPS — password in logs\n\nEK9:\n  dbPassword <- env.sensitiveGet(\"DB_PASSWORD\")   returns Sensitive type\n  stdout.println($dbPassword)                     prints ***REDACTED***\n\nThe Sensitive type wraps the value. When you convert to String with $, it shows ***REDACTED***. To access the actual value, you need the Privileged trait — and the compiler tracks which classes have it.\n\nFOR CONFIGURATION (non-secret):\n  dbHost <- env.get(\"DB_HOST\")              returns plain String\n  dbPort <- env.get(\"DB_PORT\")              returns plain String\n\nFOR SECRETS:\n  dbPassword <- env.sensitiveGet(\"DB_PASSWORD\")   returns Sensitive\n  apiKey <- env.sensitiveGet(\"API_KEY\")            returns Sensitive\n\nThe compiler prevents you from accidentally using sensitiveGet() values as plain strings — they stay wrapped in Sensitive until explicitly revealed by a Privileged class.","ek9Example":"defines module qa.security.sensitivecredentials\n\n  defines program\n\n    CredentialsDemo()\n      stdout <- Stdout()\n\n      // Non-secret configuration: use env.get()\n      appName <- \"MyService\"\n      stdout.println(`App: ${appName}`)\n\n      // In production, secrets use env.sensitiveGet()\n      // Only classes with 'trait of Privileged' can call reveal()\n      stdout.println(\"Secrets are protected by the Sensitive type\")","migrationContext":"Python developers: os.environ values are plain strings. EK9 separates config (env.get) from secrets (env.sensitiveGet). Secrets are compile-time protected against accidental exposure.","keywords":["api key","credential","environment","password","redacted","secret","sensitive"],"primaryTopics":["Sensitive type for credentials","env.sensitiveGet"],"typicalErrors":[],"companions":[]}
{"id":1017,"category":"Streams and Pipelines","question":"When should I use a stream pipeline instead of a loop in EK9?","url":"https://ek9.io/qa/QA1017.html","alternatePhrasings":["How do I replace a for loop with a stream pipeline?","What loop patterns should be rewritten as streams in EK9?","Show me a loop and its stream equivalent side by side"],"answer":"Use a stream pipeline when you are filtering, transforming, or collecting items. Use a loop when you need mutation or sequential state between iterations.\n\nLOOP PATTERN → STREAM REPLACEMENT:\n\nFilter and collect:\n  LOOP: for item in items / if condition / results += item\n  STREAM: cat items | filter by condition | collect as List of T\n\nTransform all items:\n  LOOP: for item in items / transformed += convert(item)\n  STREAM: cat items | map with convert | collect as List of T\n\nFind first N matching:\n  LOOP: for item in items / if condition / results += item / if count >= limit / (no break in EK9)\n  STREAM: cat items | filter by condition | head N | collect as List of T\n\nThe stream version is shorter, clearer in intent, and the head operation replaces the break pattern naturally.","ek9Example":"defines module qa.streams.replacesloop\n\n  defines function\n\n    isHighValue() as pure\n      -> amount as Float\n      <- rtn as Boolean?\n\n      threshold <- 50.0\n      rtn: amount > threshold\n\n    formatAmount() as pure\n      -> amount as Float\n      <- rtn as String: `Amount: ${amount}`\n\n  defines program\n\n    StreamReplacesLoopDemo()\n      stdout <- Stdout()\n\n      amounts <- [12.50, 75.00, 8.99, 120.00, 45.00, 89.99]\n\n      //Stream: filter high values and format\n      stdout.println(\"High value items:\")\n      cat amounts\n        | filter by isHighValue\n        | map with formatAmount\n        > stdout\n\n      //Stream: collect into new list\n      highAmounts <- cat amounts\n        | filter by isHighValue\n        | collect as List of Float\n      stdout.println(`Count: ${highAmounts.length()}`)","migrationContext":"EK9 streams replace filter+collect loop patterns. The stream states intent (filter, transform, limit) while the loop describes mechanism (iterate, check, accumulate).","keywords":["collect","filter","loop","refactor","replace","stream"],"primaryTopics":["stream vs loop","when to use stream pipeline"],"typicalErrors":[{"error":"E50060","correct":"      stdout.println(`Count: ${highAmounts.length()}`)","incorrect":"      stdout.println(`Count: ${highAmounts.length()}`)\n      highAmounts.append(89.99)","explanation":"EK9 List has no .append() method — add elements with the += operator or build the list via a stream pipeline. See ek9 -h E50060 for details."}],"companions":[]}
{"id":1018,"category":"Streams and Pipelines","question":"How do I compose multiple stream operations into a reusable pipeline in EK9?","url":"https://ek9.io/qa/QA1018.html","alternatePhrasings":["Can I break a complex pipeline into smaller reusable parts?","How do I build a multi-stage stream pipeline in EK9?","Show me a complex stream pipeline with multiple operations"],"answer":"Build complex pipelines by chaining operations with |. Each operation receives items from the previous stage.\n\nSIMPLE:\n  cat items | filter by isValid > stdout\n\nMULTI-STAGE:\n  cat items\n    | filter by isValid\n    | sort by compareByName\n    | map with formatForDisplay\n    | head 10\n    > stdout\n\nWITH SIDE CAPTURE (tee):\n  cat items\n    | filter by isValid\n    | tee in validBackup\n    | sort by compareByName\n    | head 10\n    > stdout\n\nThe tee operation copies items into a collection while continuing the pipeline. This lets you capture intermediate results without breaking the flow.\n\nReusable parts come from the functions you pass to each operation — isValid, compareByName, formatForDisplay are all standalone pure functions that can be tested independently.","ek9Example":"defines module qa.streams.pipelinecomposition\n\n  defines record\n\n    Task\n      taskName as String: String()\n      priority as Integer: 0\n      completed as Boolean: false\n\n      Task()\n        ->\n          taskName as String\n          priority as Integer\n          completed as Boolean\n        this.taskName :=: taskName\n        this.priority :=: priority\n        this.completed :=: completed\n\n      default operator\n\n  defines function\n\n    isIncomplete() as pure\n      -> task as Task\n      <- rtn as Boolean: not task.completed\n\n    compareByPriority() as pure\n      ->\n        left as Task\n        right as Task\n      <- rtn as Integer: left.priority <=> right.priority\n\n  defines program\n\n    PipelineCompositionDemo()\n      stdout <- Stdout()\n\n      tasks <- [\n        Task(\"Write tests\", 2, false),\n        Task(\"Fix bug\", 1, true),\n        Task(\"Review PR\", 1, false),\n        Task(\"Deploy\", 3, false),\n        Task(\"Update docs\", 2, true)\n      ]\n\n      //Multi-stage: filter incomplete, sort by priority, take top 2\n      stdout.println(\"Top priority incomplete tasks:\")\n      cat tasks\n        | filter by isIncomplete\n        | sort by compareByPriority\n        | head 2\n        > stdout","migrationContext":"EK9 pipeline composition uses named pure functions at each stage. Each function is independently testable and reusable across different pipelines.","keywords":["compose","multi-stage","pipeline","reusable","stream","tee"],"primaryTopics":["pipeline composition","multi-stage stream"],"typicalErrors":[],"companions":[]}
{"id":1019,"category":"Collections and Data Structures","question":"Is an empty List set or unset in EK9? What about an empty Dict?","url":"https://ek9.io/qa/QA1019.html","alternatePhrasings":["Are empty collections considered set in EK9?","What is the difference between an empty list and an unset list?","When I create List() of String, is it set?"],"answer":"Empty collections are ALWAYS SET in EK9. Creating a collection makes it set, even with zero items.\n\n  emptyList <- List() of String\n  emptyList?                         true - empty list IS set\n\n  emptyDict <- Dict() of (String, Integer)\n  emptyDict?                         true - empty dict IS set\n\nThis is different from primitives where an uninitialised value is unset:\n  unsetName <- String()\n  unsetName?                         false - no value assigned\n\nWHY COLLECTIONS ARE ALWAYS SET:\nAn empty collection is a valid, meaningful state - it means 'I looked and found nothing.' An unset collection would mean 'I haven't looked yet.' EK9 keeps this distinction clear.\n\nCHECK EMPTINESS WITH 'is empty':\n  if myList is empty\n    stdout.println(\"List has no items\")\n  if myList is not empty\n    stdout.println(\"List has items\")\n\nDo not confuse empty (no items) with unset (no value). An empty list is set but empty.","ek9Example":"defines module qa.collections.alwaysset\n\n  defines function\n\n    fetchNames()\n      -> searchTerm as String\n      <- rtn as List of String: List() of String\n\n      if searchTerm == \"team\"\n        rtn += \"Alice\"\n        rtn += \"Bob\"\n\n  defines program\n\n    CollectionsAlwaysSetDemo()\n      stdout <- Stdout()\n\n      //Fetch with results\n      teamNames <- fetchNames(\"team\")\n      stdout.println(`Team set?: ${teamNames?}`)\n      if teamNames is not empty\n        stdout.println(`Team has ${teamNames.length()} members`)\n\n      //Fetch with no results - still SET, just empty\n      unknownNames <- fetchNames(\"unknown\")\n      stdout.println(`Unknown set?: ${unknownNames?}`)\n      if unknownNames is empty\n        stdout.println(\"No results found - but list IS set\")\n\n      //Compare with unset primitive\n      unsetName <- String()\n      stdout.println(`Unset string set?: ${unsetName?}`)","migrationContext":"In EK9, creating a collection makes it set immediately. Empty is not unset. Use 'is empty' or 'is not empty' to check for zero items.","keywords":["always set","collection","dict","empty","list","set","unset"],"primaryTopics":["collections always set","empty vs unset"],"typicalErrors":[{"error":"E50060","correct":"      if unknownNames is empty","incorrect":"      if unknownNames.isEmpty()","explanation":"EK9 uses 'is empty' / 'is not empty', not a '.isEmpty()' method. See ek9 -h E50060 for details."}],"companions":[]}
{"id":1020,"category":"Code Quality","question":"My function is too complex and the compiler rejects it. How do I refactor to reduce complexity?","url":"https://ek9.io/qa/QA1020.html","alternatePhrasings":["How do I fix a complexity error in EK9?","What techniques reduce cyclomatic complexity in EK9?","The compiler says my method exceeds the complexity limit. How do I split it?"],"answer":"EK9 enforces complexity limits at compile time. If your function exceeds the threshold, extract smaller named functions.\n\nTECHNIQUES:\n\n1. EXTRACT GUARD FUNCTIONS\nBefore: one function with 5 conditions\nAfter: 5 small pure functions, each checking one condition, called from a simple pipeline or sequence\n\n2. USE STREAM PIPELINE\nBefore: nested for loop with if/else inside\nAfter: cat source | filter by condition | map with transform | collect\nEach pipeline operation is a separate function — complexity distributed across small functions\n\n3. USE SWITCH EXPRESSION\nBefore: chain of if/else-if/else-if\nAfter: switch with case clauses — switch is a single control flow construct, lower complexity than chained if/else\n\n4. EXTRACT HELPER FUNCTIONS\nBefore: one long function doing 3 things\nAfter: 3 named functions called in sequence — each under the limit\n\nThe compiler counts branches (if, else, case, catch, for, while) per function. Keep each function focused on one task.","ek9Example":"defines module qa.codequality.refactorcomplexity\n\n  defines function\n\n    //BEFORE: complex condition inline\n    //isEligible <- age > 18 and score > cutoff and active and not suspended\n    //This contributes 4 branches to the containing function\n\n    //AFTER: extract each check into a named function\n    isAdult() as pure\n      -> age as Integer\n      <- rtn as Boolean?\n      adultAge <- 18\n      rtn: age > adultAge\n\n    meetsScoreThreshold() as pure\n      ->\n        score as Integer\n        cutoff as Integer\n      <- rtn as Boolean: score > cutoff\n\n    isActiveAccount() as pure\n      -> active as Boolean\n      <- rtn as Boolean: active\n\n  defines program\n\n    RefactorDemo()\n      stdout <- Stdout()\n\n      age <- 25\n      score <- 85\n      cutoff <- 70\n      active <- true\n\n      //Clean: each check is a named function\n      eligible <- isAdult(age) and meetsScoreThreshold(score, cutoff) and isActiveAccount(active)\n      stdout.println(`Eligible: ${eligible}`)","migrationContext":"EK9 enforces complexity limits at compile time. Extract small pure functions and use stream pipelines to distribute complexity.","keywords":["complexity","cyclomatic","extract","limit","quality","refactor"],"primaryTopics":["refactoring for complexity","reducing cyclomatic complexity"],"typicalErrors":[],"companions":[]}
{"id":1021,"category":"Control Flow","question":"How do I skip items in a loop in EK9 without continue?","url":"https://ek9.io/qa/QA1021.html","alternatePhrasings":["What replaces continue in EK9?","How do I skip certain items when processing a collection?","EK9 has no continue — what do I use instead?"],"answer":"EK9 has no continue statement. Use a stream pipeline with 'filter by' to keep only the items you want, or 'reject by' to remove the items you don't want.\n\nOTHER LANGUAGE (with continue):\n  for item in items\n    if not isValid(item)\n      continue\n    process(item)\n\nEK9 (with filter):\n  cat items | filter by isValid > stdout\n\nEK9 (with reject — inverse of filter):\n  cat items | reject by isInvalid > stdout\n\nFor loops where you need to skip based on a condition but also do work on each item:\n  for item in items\n    if isValid(item)\n      process(item)\n\nThis is a simple if inside a for — no continue needed. The EK9 indentation makes the control flow clear.","ek9Example":"defines module qa.controlflow.replacecontinue\n\n  defines function\n\n    isEvenNumber() as pure\n      -> number as Integer\n      <- rtn as Boolean: number mod 2 == 0\n\n  defines program\n\n    ReplaceContinueDemo()\n      stdout <- Stdout()\n\n      numbers <- [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]\n\n      //Stream approach: filter keeps only even numbers\n      stdout.println(\"Even numbers (stream):\")\n      cat numbers | filter by isEvenNumber > stdout\n\n      //Loop approach: if condition replaces continue\n      stdout.println(\"Even numbers (loop):\")\n      for number in numbers\n        if isEvenNumber(number)\n          stdout.println($number)","migrationContext":"EK9 replaces continue with filter in stream pipelines or simple if conditions in loops. The intent is clearer without continue.","keywords":["continue","filter","loop","reject","replace","skip"],"primaryTopics":["replace continue","filter instead of continue"],"typicalErrors":[{"error":"E01071","correct":"stdout.println($number)","incorrect":"continue","explanation":"'continue' does not exist in EK9 — use 'filter by' in a stream pipeline or an 'if' condition inside the loop to skip items. See ek9 -h E01071 for details."}],"companions":[]}
{"id":1022,"category":"Control Flow","question":"Can I use guard expressions in switch statements and while loops, not just if?","url":"https://ek9.io/qa/QA1022.html","alternatePhrasings":["Show me guards in a switch statement in EK9","How do guards work in while loops in EK9?","Do EK9 guards work in all control flow constructs?"],"answer":"Yes. Guard expressions work identically in if, switch, while, do-while, and try. The pattern is always 'v <- expression' which declares v and only enters the block if v is SET.\n\nGUARD IN IF:\n  if name <- findUser(userId)\n    stdout.println(name)\n\nGUARD IN SWITCH:\n  switch record <- fetchRecord(recordId) with record\n    case .category == \"ACTIVE\"\n      processActive(record)\n    default\n      processOther(record)\n\nGUARD IN WHILE:\n  while item <- iterator.next()\n    process(item)\n\nGUARD IN TRY:\n  try connection <- openDatabase()\n    query(connection)\n  catch\n    -> ex as Exception\n    handleError(ex)\n\nAll four patterns follow the same rule: the block only executes if the guard expression returns a SET value. The declared variable is only available inside the block.","ek9Example":"defines module qa.controlflow.guardsswitchwhile\n\n  defines function\n\n    lookupStatus() as pure\n      -> code as String\n      <- rtn as String: String()\n\n      activeCode <- \"ACT\"\n      pendingCode <- \"PND\"\n\n      if code == activeCode\n        rtn: \"Active\"\n      else if code == pendingCode\n        rtn: \"Pending\"\n\n  defines program\n\n    GuardsSwitchWhileDemo()\n      stdout <- Stdout()\n\n      //Guard in if\n      if status <- lookupStatus(\"ACT\")\n        stdout.println(`Status: ${status}`)\n\n      //Guard in if with else (unset path)\n      if unknown <- lookupStatus(\"XXX\")\n        stdout.println(`Found: ${unknown}`)\n      else\n        stdout.println(\"Status not found\")\n\n      //Guard in switch\n      switch found <- lookupStatus(\"PND\") with found\n        case == \"Active\"\n          stdout.println(\"Is active\")\n        case == \"Pending\"\n          stdout.println(\"Is pending\")\n        default\n          stdout.println(`Other: ${found}`)","migrationContext":"EK9 guards are universal — same syntax in if, switch, while, and try. One pattern to learn, works everywhere.","keywords":["control flow","guard","switch","try","universal","while"],"primaryTopics":["guards in switch","guards in while","universal guards"],"typicalErrors":[{"error":"E01073","correct":"      <- rtn as String: String()","incorrect":"      <- rtn as String: null","explanation":"'null' does not exist in EK9; an unset String is `String()`, not `null`, which triggers E01073 - use tri-state (unset/set) semantics. See ek9 -h E01073 for details."}],"companions":[]}
{"id":1023,"category":"Control Flow","question":"What is the difference between :=, ?=, and :=? guard operators in EK9?","url":"https://ek9.io/qa/QA1023.html","alternatePhrasings":["When should I use := vs ?= vs :=? in a switch statement?","How do the three assignment guard operators differ in EK9?","What guard operator should I use in EK9 control flow?","Show me all three guard operators side by side in EK9"],"answer":"EK9 has three assignment guard operators with distinct semantics for EXISTING variables. Each checks something different before proceeding with the control flow body.\n\n:= (BLIND ASSIGNMENT)\nAlways evaluates expression. Always assigns. No safety checks.\n  switch mainValue := getValue()\n    case 1 ...\nUse when: Value is known safe, or an explicit 'with' condition validates it.\n\n?= (GUARDED ASSIGNMENT - checks RIGHT side)\nAlways evaluates expression. Checks if result is SET. Only assigns if SET.\n  switch mainValue ?= getValue()\n    case 1 ...\nUse when: Expression might return unset (network call, lookup, parse). Protects against assigning bad data.\n\n:=? (ASSIGNMENT IF UNSET - checks LEFT side)\nFirst checks if target is already set. Only evaluates expression if target is UNSET. Then checks result before assigning.\n  switch mainValue :=? getValue()\n    case 1 ...\nUse when: Variable may already have a value (caching, defaults, priority chains). Avoids re-evaluation.\n\nDECISION TREE:\n  First time using this variable? -> Use <- (declaration guard)\n  Variable exists, keep if already set? -> Use :=? (lazy, avoids re-evaluation)\n  Variable exists, need to validate new value? -> Use ?= (checks result quality)\n  Variable exists, value known safe? -> Use := (blind, no checks)\n\nSee Q74 for declaration guard (<-). See Q79 for :=? standalone usage. See Q759 for ?= as Boolean expression. See Q1024 for ?= across control flow. See Q1025 for := vs :=? across control flow.","ek9Example":"defines module qa.controlflow.guard.operator.contrast\n\n  defines function\n\n    getZero()\n      <- rtn as Integer?\n      rtn: 0\n\n    getUnsetValue()\n      <- rtn as Integer?\n      rtn: Integer()\n\n    getOne()\n      <- rtn as Integer?\n      rtn: 1\n\n  defines program\n\n    GuardOperatorContrastDemo()\n      stdout <- Stdout()\n\n      // === := BLIND ASSIGNMENT ===\n      // Always assigns. getZero() returns 0, matches no case -> default.\n      blindResult <- Integer()\n\n      switch blindResult := getZero()\n        case 1\n          stdout.println(\":= matched case 1\")\n        default\n          stdout.println(`:= hit default (value: ${blindResult})`)\n\n      // === ?= GUARDED ASSIGNMENT (checks RIGHT side) ===\n      // Evaluates getUnsetValue() which returns an unset Integer.\n      // Because result is NOT set, assignment is SKIPPED and switch body is SKIPPED.\n      guardedResult <- Integer()\n\n      switch guardedResult ?= getUnsetValue()\n        case 1\n          stdout.println(\"?= matched case 1\")\n        default\n          stdout.println(\"?= hit default\")\n\n      if guardedResult?\n        stdout.println(\"?= assigned a value\")\n      else\n        stdout.println(\"?= skipped (RHS unset, no assignment)\")\n\n      // === :=? ASSIGNMENT IF UNSET (checks LEFT side) ===\n      // lazyResult starts unset, so :=? WILL evaluate getOne().\n      // getOne() returns 1, which is set, so assignment happens and matches case 1.\n      lazyResult <- Integer()\n\n      switch lazyResult :=? getOne()\n        case 1\n          stdout.println(`:=? assigned (value: ${lazyResult})`)\n        default\n          stdout.println(\":=? hit default\")","migrationContext":"Java: No equivalent. Must manually write if/else chains for each pattern. Optional.orElse() handles one level. Kotlin: Elvis (?:) handles null-only cases, not tri-state isSet. Rust: if let handles pattern matching, no assignment-if-unset. Go: if err := f(); err != nil — closest to := but no ?= or :=? equivalents. EK9: Three operators cover declaration, blind assignment, quality-checked assignment, and lazy assignment — all in one unified syntax across all control flow.","keywords":["assignment","blind","choose","comparison","contrast","control","decision","difference","flow","guard","guarded","lazy","operator","switch","unset"],"primaryTopics":["guard operator comparison","switch guard operators","choosing guard operator"],"typicalErrors":[{"error":"E01073","correct":"if guardedResult?","incorrect":"if guardedResult <> null","explanation":"EK9 has no null; use the tri-state '?' isSet operator (or a guard) instead of comparing to null. See ek9 -h E01073 for details."}],"companions":[]}
{"id":1024,"category":"Control Flow","question":"How does the ?= guarded assignment protect against unset values in control flow?","url":"https://ek9.io/qa/QA1024.html","alternatePhrasings":["How does ?= work in if and switch statements?","What happens when ?= gets an unset value in EK9?","How do I safely assign from a function that might return unset?","Show me ?= guarded assignment in switch and if in EK9"],"answer":"The ?= operator ALWAYS evaluates the right side expression first. It then checks if the result is SET (not null and isSet returns true). Only if the result IS set does it assign to the target variable and proceed into the control flow body.\n\nIN IF:\n  if data ?= fetchRecord(id)\n    process(data)\nfetchRecord is called. If the result is set, data is assigned and the body executes. If unset, the body is skipped entirely.\n\nIN SWITCH:\n  switch value ?= parseInput(text)\n    case 1 ...\nparseInput is called. If result is unset, the entire switch (including default) is skipped. No bad data is assigned.\n\nKEY INSIGHT: ?= protects the TARGET variable from receiving unset data. If the expression returns something unset, the target stays unchanged. This is critical for:\n  - Network calls that might fail\n  - Database lookups that might find nothing\n  - Parsing that might produce invalid results\n\nCOMPARISON:\n  := always assigns (no protection)\n  ?= checks result BEFORE assigning (protects against bad data)\n  :=? checks target BEFORE evaluating (lazy, avoids unnecessary work)\n\nSee Q1023 for all three operators compared. See Q759 for ?= returning Boolean. See Q74 for <- declaration guard.","ek9Example":"defines module qa.controlflow.guarded.assignment.flow\n\n  defines function\n\n    fetchSetValue()\n      <- rtn as Integer?\n      rtn: 42\n\n    fetchUnsetValue()\n      <- rtn as Integer?\n      rtn: Integer()\n\n  defines program\n\n    GuardedAssignmentFlowDemo()\n      stdout <- Stdout()\n\n      // === ?= IN IF: protects against unset ===\n      existing <- Integer()\n\n      if existing ?= fetchSetValue()\n        stdout.println(\"IF ?= with set value: assigned \" + $existing)\n\n      // Reset for next demo\n      existing: Integer()\n\n      if existing ?= fetchUnsetValue()\n        stdout.println(\"This should not print\")\n      else\n        stdout.println(\"IF ?= with unset value: body skipped\")\n\n      // === ?= IN SWITCH: skips entire switch when unset ===\n      switchControl <- Integer()\n\n      switch switchControl ?= fetchUnsetValue()\n        case 42\n          stdout.println(\"Switch matched 42\")\n        default\n          stdout.println(\"Switch hit default\")\n\n      if switchControl?\n        stdout.println(\"Switch entered with value\")\n      else\n        stdout.println(\"Switch skipped entirely (RHS unset)\")\n\n      // === ?= IN SWITCH: enters switch when set ===\n      switchValid <- Integer()\n\n      switch switchValid ?= fetchSetValue()\n        case 42\n          stdout.println(\"Switch matched 42 from set value\")\n        default\n          stdout.println(\"Switch hit default from set value\")","migrationContext":"Java: Must write 'var temp = fetch(); if (temp != null && temp.isValid()) { data = temp; switch(data) {...} }'. EK9: 'switch data ?= fetch()' does all of this in one line. Rust: if let Some(v) = expr — similar concept but no assignment to existing variable. Go: No equivalent — must check err separately.","keywords":["assignment","check","control","flow","guard","guarded","if","isset","null-safe","protect","safe","switch","unset","validate"],"primaryTopics":["?= in control flow","guarded assignment protection","unset value handling"],"typicalErrors":[{"error":"E01073","correct":"      if existing ?= fetchSetValue()","incorrect":"      if existing != null","explanation":"'null' does not exist in EK9 - use '?=' to evaluate, isSet-check and conditionally assign in one step. See ek9 -h E01073 for details."}],"companions":[]}
{"id":1025,"category":"Control Flow","question":"How do := and :=? work differently as guard operators in EK9 control flow?","url":"https://ek9.io/qa/QA1025.html","alternatePhrasings":["When should I use := vs :=? in a while loop or if statement?","What is the difference between blind assignment and assignment-if-unset in guards?","How does :=? avoid re-evaluation in EK9 control flow?","Show me := vs :=? in if and while in EK9"],"answer":"The key difference is WHAT gets checked and WHEN the expression evaluates.\n\n:= (BLIND ASSIGNMENT)\nAlways evaluates the expression. Always assigns. No isSet checks.\n  if count := getCount() with count > 0\n    process(count)\ngetCount() is called unconditionally. count is assigned unconditionally. The 'with' condition is the only gate.\n\n:=? (ASSIGNMENT IF UNSET)\nFirst checks if the target variable is already SET. Only evaluates the expression if the target is UNSET. Then checks the result before assigning.\n  if cache :=? expensiveCompute() with cache > threshold\n    useCache(cache)\nIf cache already has a value, expensiveCompute() is NEVER called. This is lazy evaluation.\n\nWHEN TO USE :=\n  - Fetching fresh data every time (sensor readings, API polls)\n  - The value changes between calls and you want the latest\n  - Combined with 'with' condition for validation\n\nWHEN TO USE :=?\n  - Caching: avoid re-computing if already have a result\n  - Configuration defaults: try sources in priority order\n  - First-match: assign from first source that provides a value\n  - Loop with lazy init: only initialize on first iteration\n\nSee Q1023 for all three operators compared. See Q79 for :=? standalone. See Q1024 for ?= protection.","ek9Example":"defines module qa.controlflow.assign.vs.assignifunset\n\n  defines function\n\n    fetchFreshData()\n      <- rtn as Integer?\n      rtn: 10\n\n    expensiveCompute()\n      <- rtn as Integer?\n      rtn: 99\n\n  defines program\n\n    AssignVsAssignIfUnsetDemo()\n      stdout <- Stdout()\n      minCounter <- 5\n      threshold <- 50\n\n      // === := BLIND ASSIGNMENT in IF ===\n      // Always evaluates, always assigns.\n      counter <- Integer()\n\n      if counter := fetchFreshData() with counter > minCounter\n        stdout.println(\":= fetched and condition met: \" + $counter)\n\n      // === :=? ASSIGNMENT IF UNSET in IF ===\n      // cache starts unset -> expensiveCompute() IS called\n      cache <- Integer()\n\n      if cache :=? expensiveCompute() with cache > threshold\n        stdout.println(\":=? computed (was unset): \" + $cache)\n\n      // cache is now set to 99 -> expensiveCompute() is NOT called again\n      if cache :=? expensiveCompute() with cache > threshold\n        stdout.println(\":=? skipped compute (already set): \" + $cache)\n\n      // === := ALWAYS RE-FETCHES (contrast with :=?) ===\n      // counter already has value 10, but := overwrites it\n      if counter := fetchFreshData() with counter > minCounter\n        stdout.println(\":= re-fetched (always evaluates): \" + $counter)","migrationContext":"Java: ':=' is like simple assignment 'x = expr;'. ':=?' requires 'if (x == null || !x.isSet()) { var temp = expr(); if (temp != null && temp.isSet()) x = temp; }' — 4 lines vs 1. Go: No ':=?' equivalent. Must write 'if x == nil { x = compute() }'. Python: No direct equivalent. 'x = x or compute()' breaks for falsy values.","keywords":["assign","blind","cache","compare","conditional","control","default","evaluation","flow","guard","if","lazy","operator","unset","while"],"primaryTopics":[":= vs :=? in control flow","blind vs conditional assignment","lazy evaluation guard"],"typicalErrors":[{"error":"E01072","correct":"      rtn: 10","incorrect":"      return 10","explanation":"'return' does not exist in EK9 — declare the return value with '<-' and assign it with 'rtn:'. See ek9 -h E01072 for details."}],"companions":[]}
{"id":1026,"category":"Getting Started","question":"Review this class. Are the names meaningful and consistent?","url":"https://ek9.io/qa/QA1026.html","alternatePhrasings":["Is the naming in this code good enough?","Check if my variable and method names are clear","Review my naming choices in this EK9 class"],"answer":"The naming is mostly good but has two issues:\n\n1. 'proc' is too abbreviated — rename to 'processOrder' or 'fulfil'. Method names should describe the action clearly.\n\n2. 'x' for the discount amount is meaningless — rename to 'discountAmount' or 'savings'. Local variables should describe what the value represents.\n\nThe rest is fine:\n- 'Order' is a clear class name\n- 'totalPrice' and 'customerName' are descriptive fields\n- 'applyDiscount' clearly states the action\n- 'discountRate' describes the parameter's role\n\nGood naming makes code self-documenting. A reader should understand the purpose of each variable without reading the implementation.","ek9Example":"defines module qa.gettingstarted.reviewernaming\n\n  defines class\n\n    Order\n      totalPrice as Float?\n      customerName as String?\n\n      default private Order()\n\n      Order()\n        ->\n          name as String\n          price as Float\n        this.customerName: name\n        this.totalPrice: price\n\n      applyDiscount()\n        -> discountRate as Float\n        <- discountAmount as Float: Float()\n\n        if discountRate?\n          discountAmount: totalPrice * discountRate\n\n      override operator ? as pure\n        <- rtn as Boolean: customerName? and totalPrice?\n\n      operator $ as pure\n        <- rtn as String: `Order for ${customerName}`\n\n  defines program\n\n    NamingReviewDemo()\n      stdout <- Stdout()\n\n      order <- Order(\"Alice\", 100.0)\n      saving <- order.applyDiscount(0.15)\n      stdout.println(`${order} saving: ${saving}`)","migrationContext":"EK9 naming follows the same clean-code principles as any language. Use descriptive names that state purpose.","keywords":["clean","naming","quality","readable","review"],"primaryTopics":["naming quality","code review"],"typicalErrors":[{"error":"E11031","correct":"      saving <- order.applyDiscount(0.15)\n      stdout.println(`${order} saving: ${saving}`)","incorrect":"      val <- order.applyDiscount(0.15)\n      stdout.println(`${order} saving: ${val}`)","explanation":"The name 'val' is one of the banned non-descriptive variable names, so 'val <- ...' triggers E11031 — use a descriptive name like saving. See ek9 -h E11031 for details."}],"companions":[]}
{"id":1027,"category":"Getting Started","question":"Review this design. Should I use composition instead of a large class?","url":"https://ek9.io/qa/QA1027.html","alternatePhrasings":["Is this class doing too many things?","Should I break this class into smaller pieces?","Assess whether this class follows single responsibility"],"answer":"Yes, this class is doing too much. It handles both user validation and notification — two separate concerns.\n\nSplit it into:\n- A UserValidator class that checks names and emails\n- A Notifier class that sends messages\n- The main class delegates to both via composition\n\nBenefits of composition:\n- Each class has one responsibility\n- You can test validation without notification\n- You can swap notification implementations\n- EK9 types are closed by default, so composition is the standard extension pattern\n\nKeep each class focused on one concern. Compose them together in a coordinator class.","ek9Example":"defines module qa.gettingstarted.compositionreview\n\n  defines class\n\n    UserValidator\n      isValidName() as pure\n        -> name as String\n        <- rtn as Boolean: name.length() > 0\n\n      isValidEmail() as pure\n        -> email as String\n        <- rtn as Boolean: email.contains(\"@\")\n\n      override operator ? as pure\n        <- rtn as Boolean: true\n\n      operator $ as pure\n        <- rtn as String: \"UserValidator\"\n\n    Notifier\n      sendWelcome()\n        -> name as String\n        <- rtn as String: `Welcome ${name}`\n\n      override operator ? as pure\n        <- rtn as Boolean: true\n\n      operator $ as pure\n        <- rtn as String: \"Notifier\"\n\n  defines program\n\n    CompositionDemo()\n      stdout <- Stdout()\n\n      validator <- UserValidator()\n      notifier <- Notifier()\n\n      userName <- \"Alice\"\n      email <- \"alice@example.com\"\n\n      if validator.isValidName(userName) and validator.isValidEmail(email)\n        message <- notifier.sendWelcome(userName)\n        stdout.println(message)","migrationContext":"EK9 types are closed by default. Composition is the primary way to combine behavior from multiple sources.","keywords":["composition","design","responsibility","review","split"],"primaryTopics":["composition over inheritance","single responsibility"],"typicalErrors":[{"error":"E05030","correct":"    Notifier\n      sendWelcome()","incorrect":"    Notifier extends UserValidator\n      sendWelcome()","explanation":"EK9 types are closed by default and cannot be extended; 'extends UserValidator' fails with E05030 - use composition instead. See ek9 -h E05030 for details."}],"companions":[]}
{"id":1028,"category":"Getting Started","question":"Write a module-level documentation comment explaining what this code does for a future maintainer.","url":"https://ek9.io/qa/QA1028.html","alternatePhrasings":["Document this EK9 module for someone reading it in 6 months","Write a description of this module's purpose and design","Explain this code for documentation purposes"],"answer":"Module: inventory.pricing\n\nProvides price calculation functions for the inventory system. All functions are pure — they compute results from inputs without side effects.\n\ncalculateDiscount: Applies a percentage discount to a base price. Returns the discounted price, or the original price if the discount percentage is unset.\n\napplyTax: Adds tax to a price at the given rate. Returns the price with tax included.\n\nformatPrice: Converts a Float price to a display String with two decimal places.\n\nDesign notes:\n- All functions are marked 'as pure' for thread safety and testability\n- Guard expressions handle unset inputs gracefully\n- Functions are small and focused — each does one thing\n- The module has no state — it is a collection of utility functions","ek9Example":"defines module inventory.pricing\n\n  defines function\n\n    <?-\n      Applies a percentage discount to a base price.\n      Returns the discounted price, or the original if discount is unset.\n    -?>\n    calculateDiscount() as pure\n      ->\n        basePrice as Float\n        discountRate as Float\n      <- rtn as Float: basePrice\n\n      if discountRate?\n        rtn := basePrice - (basePrice * discountRate)\n\n    <?-\n      Adds tax to a price at the given rate.\n    -?>\n    applyTax() as pure\n      ->\n        netPrice as Float\n        taxRate as Float\n      <- rtn as Float: netPrice + (netPrice * taxRate)\n\n    <?-\n      Converts a price to a display string.\n    -?>\n    formatPrice() as pure\n      -> amount as Float\n      <- rtn as String: `${amount}`\n\n  defines program\n\n    PricingDemo()\n      stdout <- Stdout()\n\n      basePrice <- 100.0\n      discountRate <- 0.15\n      taxRate <- 0.20\n\n      discounted <- calculateDiscount(basePrice, discountRate)\n      withTax <- applyTax(discounted, taxRate)\n      stdout.println(`Price: ${formatPrice(withTax)}`)","migrationContext":"EK9 documentation describes what the code does, its design decisions, and how to use it — without comparing to other languages.","keywords":["document","explain","maintainer","module","purpose"],"primaryTopics":["module documentation","code explanation"],"typicalErrors":[{"error":"E11031","correct":"      discounted <- calculateDiscount(basePrice, discountRate)\n      withTax <- applyTax(discounted, taxRate)","incorrect":"      data <- calculateDiscount(basePrice, discountRate)\n      withTax <- applyTax(data, taxRate)","explanation":"Generic non-descriptive variable names like 'data' are banned; use a meaningful identifier such as 'discounted'. See ek9 -h E11031 for details."}],"companions":[]}
{"id":1029,"category":"Getting Started","question":"My code uses := for the first variable. Fix it with the minimum change.","url":"https://ek9.io/qa/QA1029.html","alternatePhrasings":["Quick fix: I used := instead of <- to declare a variable","What is the minimal fix when := is used before <-?","I get an error on my first assignment. What is the simplest fix?"],"answer":"Change := to <-. That is the only change needed.\n\nBEFORE (wrong):\n  greeting := \"Hello\"\n\nAFTER (fixed):\n  greeting <- \"Hello\"\n\nThe <- operator declares a new variable. The := operator updates an existing one. If the variable does not exist yet, you must use <- to create it.\n\nFor subsequent assignments to the same variable, := is correct:\n  greeting <- \"Hello\"     first time: <- to create\n  greeting := \"Hi there\"  after: := to update","ek9Example":"defines module qa.gettingstarted.quickfixes\n\n  defines program\n\n    QuickFixDemo()\n      stdout <- Stdout()\n\n      //Correct: <- to declare\n      greeting <- \"Hello\"\n      stdout.println(greeting)\n\n      //Correct: := to update\n      greeting := \"Hi there\"\n      stdout.println(greeting)","migrationContext":"The fix is always minimal: change := to <- for the first use of a variable.","keywords":["assign","declare","fix","generalist","minimal","quick"],"primaryTopics":["quick fix := to <-","minimal code fix"],"typicalErrors":[{"error":"E50001","correct":"      greeting <- \"Hello\"","incorrect":"      greeting := \"Hello\"","explanation":"Use <- to declare a new variable. Using := on a name that does not exist yet means it cannot be resolved."}],"companions":[]}
{"id":1030,"category":"Getting Started","question":"Review this function. Is it too complex?","url":"https://ek9.io/qa/QA1030.html","alternatePhrasings":["Is this code doing too much in one function?","Should I break this function into smaller pieces?","Assess the complexity of this EK9 function"],"answer":"Yes, this function is too complex. It does three things: validates input, calculates a result, and formats output. Each concern should be a separate pure function.\n\nSplit into:\n- validateInput: checks if the values are usable\n- calculateTotal: does the arithmetic\n- formatResult: converts to a display string\n\nBenefits:\n- Each function is testable in isolation\n- Pure functions can be reused elsewhere\n- The main function becomes a pipeline of clear steps\n- Complexity is distributed across small, readable units\n\nEK9 encourages small pure functions composed together. When a function has multiple responsibilities, split it.","ek9Example":"defines module qa.gettingstarted.reviewcomplexity\n\n  defines function\n\n    validateAmount() as pure\n      -> amount as Float\n      <- rtn as Boolean: amount?\n\n    calculateWithTax() as pure\n      ->\n        amount as Float\n        taxRate as Float\n      <- rtn as Float: amount + (amount * taxRate)\n\n    formatCurrency() as pure\n      -> amount as Float\n      <- rtn as String: `Total: ${amount}`\n\n  defines program\n\n    ComplexityDemo()\n      stdout <- Stdout()\n\n      amount <- 99.50\n      taxRate <- 0.20\n\n      if validateAmount(amount)\n        total <- calculateWithTax(amount, taxRate)\n        stdout.println(formatCurrency(total))\n      else\n        stdout.println(\"Invalid amount\")","migrationContext":"EK9 encourages small, pure functions composed together. Split complex functions into single-responsibility pieces.","keywords":["complexity","function","pure","review","split"],"primaryTopics":["complexity assessment","function decomposition"],"typicalErrors":[{"error":"E11031","correct":"        total <- calculateWithTax(amount, taxRate)\n        stdout.println(formatCurrency(total))","incorrect":"        value <- calculateWithTax(amount, taxRate)\n        stdout.println(formatCurrency(value))","explanation":"'value' is a banned non-descriptive variable name in EK9 — use a meaningful identifier such as 'total'. See ek9 -h E11031 for details."}],"companions":[]}
{"id":1031,"category":"Getting Started","question":"Explain the design decisions in this EK9 code for a team wiki.","url":"https://ek9.io/qa/QA1031.html","alternatePhrasings":["Document the design rationale behind this code","Write a technical explanation of this module for new team members","Explain why this code is structured this way"],"answer":"Design Rationale for UserService module:\n\nStructure: The module separates validation (pure functions) from state management (the UserService class). This separation means validation logic can be tested without setting up a service instance.\n\nValidation functions are marked 'as pure' because they only compute results from their inputs — no side effects, no state changes. This makes them safe to call from any context, including concurrent code.\n\nThe UserService class uses guard expressions in its methods. When findUser returns a value, the guard (if user <- findUser(name)) ensures the block only runs when a valid user was found. There is no null checking and no exception handling for missing users — the guard handles it.\n\nReturn values are declared at the top of each function with <- . This is how EK9 functions declare what they return — it is not a statement that causes an early exit. Every code path must initialise the return value.\n\nThe class has a default private constructor because its properties are uninitialised at construction time. Only the parameterised constructor is public.","ek9Example":"defines module qa.gettingstarted.designrationale\n\n  defines function\n\n    isValidName() as pure\n      -> name as String\n      <- rtn as Boolean: name? and name.length() > 0\n\n    isValidEmail() as pure\n      -> email as String\n      <- rtn as Boolean: email? and email.contains(\"@\")\n\n  defines class\n\n    User\n      name as String?\n      email as String?\n\n      default private User()\n\n      User()\n        ->\n          name as String\n          email as String\n        this.name: name\n        this.email: email\n\n      override operator ? as pure\n        <- rtn as Boolean: name? and email?\n\n      operator $ as pure\n        <- rtn as String: `${name} (${email})`\n\n  defines program\n\n    DesignRationaleDemo()\n      stdout <- Stdout()\n\n      name <- \"Alice\"\n      email <- \"alice@example.com\"\n\n      if isValidName(name) and isValidEmail(email)\n        user <- User(name, email)\n        stdout.println(`Created: ${user}`)","migrationContext":"EK9 design documentation explains what the code does and why, without comparing to other languages.","keywords":["design","document","explain","rationale","team","wiki"],"primaryTopics":["design documentation","code explanation"],"typicalErrors":[{"error":"E07110","correct":"      operator $ as pure\n        <- rtn as String: `${name} (${email})`","incorrect":"      operator $ as pure\n        <- rtn as String?","explanation":"A non-abstract operator or method must provide an implementation — declaring only an uninitialised return '<- rtn as String?' leaves it with no body, so give the return a value. See ek9 -h E07110 for details."}],"companions":[]}
{"id":1032,"category":"Getting Started","question":"My function uses return to send back a value. How do I fix it?","url":"https://ek9.io/qa/QA1032.html","alternatePhrasings":["Quick fix: replace return statement in EK9","EK9 has no return keyword. What do I use instead?","How do I return a value from an EK9 function?"],"answer":"EK9 has no return statement. Declare the return value with <- at the top of the function, then assign to it.\n\nBEFORE (wrong):\n  calculateArea()\n    -> radius as Float\n    return 3.14159 * radius * radius\n\nAFTER (fixed):\n  calculateArea() as pure\n    -> radius as Float\n    <- rtn as Float: 3.14159 * radius * radius\n\nThe <- rtn as Float line declares a variable called rtn that IS the return value. You can assign to it with := later in the function if needed:\n\n  findLabel()\n    -> code as Integer\n    <- rtn as String: \"unknown\"\n\n    switch code\n      case 1\n        rtn := \"active\"\n      case 2\n        rtn := \"inactive\"\n\nEvery code path must initialise the return value. The compiler enforces this — there is no way to forget.","ek9Example":"defines module qa.gettingstarted.fixreturn\n\n  defines function\n\n    calculateArea() as pure\n      -> radius as Float\n      <- rtn as Float: 3.14159 * radius * radius\n\n    findLabel() as pure\n      -> code as Integer\n      <- rtn as String: \"unknown\"\n\n      switch code\n        case 1\n          rtn := \"active\"\n        case 2\n          rtn := \"inactive\"\n        default\n          rtn := \"unknown\"\n\n  defines program\n\n    FixReturnDemo()\n      stdout <- Stdout()\n\n      area <- calculateArea(5.0)\n      stdout.println(`Area: ${area}`)\n\n      label <- findLabel(1)\n      stdout.println(`Label: ${label}`)","migrationContext":"EK9 uses declared return values instead of return statements. The compiler ensures all paths assign a value.","keywords":["declare","fix","function","quick","return"],"primaryTopics":["return value declaration","no return statement"],"typicalErrors":[{"error":"E01072","correct":"    <- rtn as Float: 3.14159 * radius * radius","incorrect":"    return 3.14159 * radius * radius","explanation":"'return' does not exist in EK9. Declare the return value with <- at the function top, then assign to it."}],"companions":[]}
{"id":1033,"category":"Getting Started","question":"I used := to create a list. How do I fix it?","url":"https://ek9.io/qa/QA1033.html","alternatePhrasings":["Quick fix: used := instead of <- for a new list","How do I declare a new list in EK9?","Fix my list creation: names := [Alice, Bob]"],"answer":"Change := to <-. Use <- for first-time declarations, including lists.\n\nBEFORE (wrong):\n  names := [\"Alice\", \"Bob\", \"Charlie\"]\n\nAFTER (fixed):\n  names <- [\"Alice\", \"Bob\", \"Charlie\"]\n\nFor an empty list, declare the type explicitly:\n  names <- List of String()\n\nFor a list with items, the type is inferred from the contents:\n  numbers <- [1, 2, 3]         List of Integer\n  prices <- [9.99, 12.50]      List of Float\n\nThe := operator is for updating an existing variable:\n  names <- [\"Alice\"]           create with <-\n  names := [\"Alice\", \"Bob\"]    update with :=","ek9Example":"defines module qa.gettingstarted.fixlistcreation\n\n  defines program\n\n    FixListDemo()\n      stdout <- Stdout()\n\n      //Correct: <- to create a new list\n      names <- [\"Alice\", \"Bob\", \"Charlie\"]\n      stdout.println(`Names: ${names}`)\n\n      //Correct: := to update an existing list\n      names := [\"Alice\", \"Bob\", \"Charlie\", \"Dave\"]\n      stdout.println(`Updated: ${names}`)\n\n      //Empty list with explicit type\n      scores <- List() of Integer\n      stdout.println(`Empty scores: ${scores}`)","migrationContext":"EK9 uses <- for all first-time declarations, including list literals. Use := only to reassign.","keywords":["collection","create","declare","fix","list","quick"],"primaryTopics":["list creation","quick fix"],"typicalErrors":[{"error":"E50001","correct":"      names <- [\"Alice\", \"Bob\", \"Charlie\"]","incorrect":"      names := [\"Alice\", \"Bob\", \"Charlie\"]","explanation":"Use <- to declare a new variable. The := operator expects the variable to already exist."}],"companions":[]}
{"id":1034,"category":"Streams and Pipelines","question":"Write a function that transforms a list of names to uppercase.","url":"https://ek9.io/qa/QA1034.html","alternatePhrasings":["How do I map a list of strings to uppercase in EK9?","Write code to convert all items in a list using a stream","Show me how to use map with in a stream pipeline"],"answer":"Use a stream pipeline with map to transform each item:\n\n  uppercased <- cat names\n    | map with toUpper\n    | collect as List of String\n\nThe 'map with' operation applies a function to each item in the stream. The function must take one argument and return one value.\n\nDefine the transformation as a named pure function:\n  toUpper() as pure\n    -> name as String\n    <- rtn as String: name.upperCase()\n\nThen use it in the pipeline. This is cleaner than a loop because:\n- The intent is clear: transform each item\n- The function is testable on its own\n- No mutable accumulator needed\n\nFor output instead of collecting:\n  cat names | map with toUpper > stdout","ek9Example":"defines module qa.streams.codertransform\n\n  defines function\n\n    toUpper() as pure\n      -> name as String\n      <- rtn as String: name.upperCase()\n\n    addGreeting() as pure\n      -> name as String\n      <- rtn as String: \"Hello, \" + name\n\n  defines program\n\n    StreamTransformDemo()\n      stdout <- Stdout()\n\n      names <- [\"alice\", \"bob\", \"charlie\"]\n\n      //Transform to uppercase\n      uppercased <- cat names\n        | map with toUpper\n        | collect as List of String\n      stdout.println(`Uppercased: ${uppercased}`)\n\n      //Chain transformations\n      cat names\n        | map with toUpper\n        | map with addGreeting\n        > stdout","migrationContext":"EK9 uses 'cat source | map with fn | collect as Type' for list transformations. Named functions replace lambdas.","keywords":["coder","map","pipeline","stream","transform","uppercase"],"primaryTopics":["stream map","list transformation"],"typicalErrors":[{"error":"E01010","correct":"      uppercased <- cat names\n        | map with toUpper\n        | collect as List of String","incorrect":"      uppercased <- names.map(n -> n.upperCase())","explanation":"EK9 streams use pipe syntax with named functions. Write 'cat source | map with functionName | collect as Type'."}],"companions":[]}
{"id":1035,"category":"Streams and Pipelines","question":"Write code to filter a list and collect the results into a new list.","url":"https://ek9.io/qa/QA1035.html","alternatePhrasings":["How do I filter items from a list into a new list in EK9?","Show the EK9 way to select matching items from a collection","Write a stream pipeline that filters and collects"],"answer":"Use a stream pipeline with filter and collect:\n\n  longNames <- cat names\n    | filter by isLongName\n    | collect as List of String\n\nThe filter function must return Boolean. Items where the function returns true pass through.\n\n  isLongName() as pure\n    -> name as String\n    <- rtn as Boolean: name.length() > 4\n\nYou can combine filter with other operations:\n  cat names\n    | filter by isLongName\n    | sort\n    | head 3\n    | collect as List of String\n\nThis reads naturally: take names, keep long ones, sort them, take first 3, collect into a list.\n\nFor counting matches, use the collected list:\n  matches <- cat items | filter by predicate | collect as List of T\n  count <- matches.length()","ek9Example":"defines module qa.streams.coderfiltercollect\n\n  defines function\n\n    isLongName() as pure\n      -> name as String\n      <- rtn as Boolean?\n\n      minLength <- 4\n      rtn: name.length() > minLength\n\n  defines program\n\n    FilterCollectDemo()\n      stdout <- Stdout()\n\n      names <- [\"Al\", \"Alice\", \"Bob\", \"Charlie\", \"Dave\", \"Elizabeth\"]\n\n      //Filter and collect\n      longNames <- cat names\n        | filter by isLongName\n        | collect as List of String\n      stdout.println(`Long names: ${longNames}`)\n\n      //Filter, sort, and take first 2\n      topTwo <- cat names\n        | filter by isLongName\n        | sort\n        | head 2\n        | collect as List of String\n      stdout.println(`Top two: ${topTwo}`)","migrationContext":"EK9 uses 'cat source | filter by fn | collect as Type' instead of loops with conditionals.","keywords":["coder","collect","filter","list","pipeline","stream"],"primaryTopics":["stream filter","collect as list"],"typicalErrors":[{"error":"E01010","correct":"      longNames <- cat names\n        | filter by isLongName\n        | collect as List of String","incorrect":"      longNames <- []\n      for name in names\n        if name.length() > 4\n          longNames.append(name)","explanation":"EK9 has no .append() or [] empty literal. Use a stream pipeline: cat source | filter by fn | collect as Type."}],"companions":[]}
{"id":1036,"category":"Getting Started","question":"Plan a search feature in EK9. What constructs should I use?","url":"https://ek9.io/qa/QA1036.html","alternatePhrasings":["How should I structure a search feature in EK9?","What EK9 patterns work best for implementing search?","Plan the architecture for a search module in EK9"],"answer":"Search feature plan using EK9 constructs:\n\n1. Define the search criteria as a record:\n   SearchCriteria with keyword as String and maxResults as Integer\n   Records are immutable data carriers — perfect for search parameters.\n\n2. Define the result type as a record:\n   SearchResult with title as String and score as Float\n   Immutable results can be safely passed between components.\n\n3. Write a pure matching function:\n   matchesCriteria() as pure\n     Takes an item and criteria, returns Boolean.\n   Pure functions are testable without mocking.\n\n4. Use a stream pipeline to search:\n   results <- cat allItems\n     | filter by matchesCriteria\n     | sort by score\n     | head maxResults\n     | collect as List of SearchResult\n   The pipeline expresses the search algorithm declaratively.\n\n5. Use guard expressions for the caller:\n   if results <- performSearch(criteria)\n     Display results\n   The guard handles the case where no results are found.\n\nKey EK9 choices:\n- Records for data (immutable, automatic operators)\n- Pure functions for logic (testable, composable)\n- Streams for collection processing (declarative, no loops)\n- Guards for result handling (null-safe, concise)","ek9Example":"defines module qa.gettingstarted.plansearch\n\n  defines function\n\n    matchesKeyword() as pure\n      -> title as String\n      <- rtn as Boolean: title.length() > 0\n\n    formatTitle() as pure\n      -> title as String\n      <- rtn as String: `Found: ${title}`\n\n  defines program\n\n    SearchDemo()\n      stdout <- Stdout()\n\n      titles <- [\"EK9 Streams Guide\", \"EK9 Guard Patterns\", \"Getting Started\"]\n\n      //Stream pipeline for search\n      cat titles\n        | filter by matchesKeyword\n        | map with formatTitle\n        > stdout","migrationContext":"EK9 planning uses records for data, pure functions for logic, streams for collection processing, and guards for result handling.","keywords":["architecture","guard","plan","record","search","stream"],"primaryTopics":["feature planning","EK9 construct selection"],"typicalErrors":[],"companions":[]}
{"id":1037,"category":"Getting Started","question":"Plan a refactoring to improve this code. What EK9 constructs should I use?","url":"https://ek9.io/qa/QA1037.html","alternatePhrasings":["How should I refactor this procedural code into idiomatic EK9?","What is the EK9 way to restructure this code?","Plan an EK9 refactoring with specific construct recommendations"],"answer":"Refactoring plan using EK9 constructs:\n\n1. Extract pure functions for each calculation:\n   Move validation and computation into separate pure functions. Pure functions are easier to test and can be reused in stream pipelines.\n\n2. Replace loops with stream pipelines:\n   If you are filtering or transforming a collection, use:\n     cat items | filter by predicate | map with transform | collect as List of T\n   Streams express intent more clearly than loops.\n\n3. Use switch expressions instead of if-else chains:\n   switch value\n     case 1\n       handleOne()\n     case 2, 3\n       handleFewCases()\n     default\n       handleOther()\n   Multiple case values on one line replace fallthrough.\n\n4. Use guard expressions for conditional logic:\n   if result <- computeSomething()\n     process(result)\n   Guards combine computation and null-safety checking.\n\n5. Use records for data transfer:\n   Records give you automatic operators (equality, string representation, hashcode) with no boilerplate.\n\nPriority order: extract pure functions first, then replace loops with streams, then clean up conditionals.","ek9Example":"defines module qa.gettingstarted.planrefactor\n\n  defines function\n\n    isValid() as pure\n      -> price as Float\n      <- rtn as Boolean: price?\n\n    applyMarkup() as pure\n      -> price as Float\n      <- rtn as Float: price * 1.20\n\n    formatPrice() as pure\n      -> price as Float\n      <- rtn as String: `Price: ${price}`\n\n  defines program\n\n    RefactorDemo()\n      stdout <- Stdout()\n\n      prices <- [10.0, 25.50, 0.0, 42.99]\n\n      //Stream pipeline: validate, transform, format, output\n      cat prices\n        | filter by isValid\n        | map with applyMarkup\n        | map with formatPrice\n        > stdout","migrationContext":"EK9 refactoring prioritises pure function extraction, stream pipeline adoption, and guard expressions.","keywords":["guard","plan","pure","refactor","stream","switch"],"primaryTopics":["refactoring plan","EK9 construct selection"],"typicalErrors":[{"error":"E11031","correct":"      -> price as Float\n      <- rtn as String: `Price: ${price}`","incorrect":"      -> value as Float\n      <- rtn as String: `Price: ${value}`","explanation":"'value' is a banned non-descriptive variable name in EK9 — use a meaningful identifier like 'price'. See ek9 -h E11031 for details."}],"companions":[]}
{"id":1038,"category":"Control Flow","question":"How do I try multiple configuration sources and use the first one that has a value?","url":"https://ek9.io/qa/QA1038.html","alternatePhrasings":["What is the EK9 pattern for fallback configuration?","How does :=? work for default value chains?","Show me priority-ordered assignment in EK9"],"answer":"Use :=? (guarded assignment) to try sources in priority order. Each :=? only evaluates its right side if the variable is still unset.\n\n  setting <- String()\n  setting :=? loadFromEnvironment()\n  setting :=? loadFromConfigFile()\n  setting :=? loadFromDefaults()\n\nThis tries environment first. If it returns a set value, the variable is assigned and the remaining calls are skipped. If not, it tries the config file, then defaults.\n\nWhy :=? instead of :=\n  With :=, every source would be called and the last one always wins.\n  With :=?, the first source that provides a value wins. Later sources are not evaluated.\n\nThis pattern replaces:\n  if setting is not set then try next source\nwithout any explicit checking. The operator handles it.\n\nUse :=? when:\n  - You want first-wins priority ordering\n  - Later sources are expensive to evaluate\n  - You want to preserve an existing value if present","ek9Example":"defines module qa.controlflow.guardedassign.configdefaults\n\n  defines function\n\n    loadFromEnvironment()\n      <- rtn as String: String()\n      //Simulate: environment variable not set\n\n    loadFromConfigFile()\n      <- rtn as String: \"from-config\"\n\n    loadFromDefaults()\n      <- rtn as String: \"default-value\"\n\n  defines program\n\n    ConfigDefaultsDemo()\n      stdout <- Stdout()\n\n      //Priority chain: environment -> config file -> defaults\n      setting <- String()\n      setting :=? loadFromEnvironment()\n      setting :=? loadFromConfigFile()\n      setting :=? loadFromDefaults()\n\n      stdout.println(`Setting resolved to: ${setting}`)\n\n      //Second example: already has a value, so :=? skips\n      name <- loadFromEnvironment()\n      name :=? loadFromDefaults()\n      stdout.println(`Name preserved: ${name}`)","migrationContext":"Java needs explicit null checks for each source. Python uses 'x = x or next_source()' but fails with falsy values. EK9's :=? handles it correctly in one operator.","keywords":["assign","config","default","fallback","guarded","priority"],"primaryTopics":["guarded assignment","configuration defaults","priority chain"],"typicalErrors":[{"error":"E01073","correct":"      setting :=? loadFromConfigFile()","incorrect":"      if setting == null\n        setting := loadFromConfigFile()","explanation":"'null' does not exist in EK9. Use ':=?' to conditionally assign — it only evaluates and assigns when the variable is currently unset."}],"companions":[]}
{"id":1039,"category":"Control Flow","question":"When do I use <- and when do I use :=? to set a variable safely?","url":"https://ek9.io/qa/QA1039.html","alternatePhrasings":["What is the difference between <- and :=? in EK9?","Should I use declaration or guarded assignment here?","How do I choose between <- for new variables and :=? for safe reassignment?"],"answer":"They solve different problems:\n\n<- CREATES a new variable. Use it when the variable does not exist yet.\n  name <- \"Alice\"           creates name, assigns \"Alice\"\n  if greeting <- getGreeting()  creates greeting in this scope, only enters if SET\n\n:=? REASSIGNS an existing variable, but only if it is currently UNSET. Use it when the variable already exists and you want to fill in a missing value without overwriting an existing one.\n  name <- String()           creates name (unset)\n  name :=? loadFromConfig()  assigns only if name is still unset\n  name :=? \"default\"         assigns only if still unset after config\n\nKey distinction:\n  <- is about CREATION. The variable must not exist yet.\n  :=? is about SAFE UPDATE. The variable must already exist. It protects existing values.\n\nCommon mistake: using <- when you mean :=?\n  name <- loadFromConfig()      WRONG if name already exists in scope\n  name :=? loadFromConfig()     RIGHT — safely fills in if unset\n\nCommon mistake: using :=? when you mean <-\n  name :=? \"Alice\"              WRONG if name does not exist yet\n  name <- \"Alice\"               RIGHT — creates the variable\n\nIn guards (if, switch, while):\n  if name <- getGreeting()      declaration guard — creates name, enters if SET\n  if name :=? getGreeting()     guarded assign — fills name if unset, enters if SET","ek9Example":"defines module qa.controlflow.declare.vs.guardedassign\n\n  defines function\n\n    loadFromConfig()\n      <- rtn as String: String()\n      //Simulate: config returns unset\n\n    loadFromEnvironment()\n      <- rtn as String: \"env-value\"\n\n  defines program\n\n    DeclareVsGuardedDemo()\n      stdout <- Stdout()\n\n      // <- creates a new variable\n      setting <- String()\n\n      // :=? fills in if unset (config returns unset, so still unset)\n      setting :=? loadFromConfig()\n\n      // :=? fills in from next source (environment returns \"env-value\")\n      setting :=? loadFromEnvironment()\n\n      // :=? preserves existing value (already set, so \"fallback\" is ignored)\n      setting :=? \"fallback\"\n\n      stdout.println(`Setting: ${setting}`)\n\n      // Contrast: <- in a guard creates a NEW variable\n      if greeting <- loadFromEnvironment()\n        stdout.println(`Greeting: ${greeting}`)","migrationContext":"No other language has this distinction. Python's walrus := creates AND assigns. EK9 separates creation (<-) from safe update (:=?).","keywords":["assign","create","declare","difference","guarded","safe","unset"],"primaryTopics":["<- vs :=?","declaration vs guarded assignment"],"typicalErrors":[{"error":"E50001","correct":"      setting <- String()","incorrect":"      setting :=? String()","explanation":"Use <- to create a new variable; :=? only reassigns a variable that already exists, so using it first leaves the name unresolved. See ek9 -h E50001 for details."}],"companions":[]}
{"id":1040,"category":"Getting Started","question":"I used := to create a variable and got an error. Why?","url":"https://ek9.io/qa/QA1040.html","alternatePhrasings":["Why can't I use := to make a new variable in EK9?","The compiler says my variable doesn't exist but I used :=","In Python := creates a variable. Why doesn't it work in EK9?"],"answer":"In EK9, := NEVER creates a variable. It only updates one that already exists.\n\nThe rule is simple:\n  <- creates a new variable (declaration)\n  := updates an existing variable (reassignment)\n\nThey are completely separate operations. There is no overlap.\n\nBEFORE (error):\n  count := 10           error: count does not exist yet\n\nAFTER (correct):\n  count <- 10           creates count with value 10\n  count := 20           later: updates count to 20\n\nThis is different from Python where := (walrus) creates AND assigns inside expressions. In EK9, := is a plain assignment statement that requires the variable to already exist.\n\nThis is also different from Go where := creates a new variable. In EK9, := never creates — only <- creates.\n\nSee Q1041 for Python developers. See Q1042 for Go developers. See Q1029 for a quick fix example. See Q965 for the full assignment operators guide.","ek9Example":"defines module qa.gettingstarted.assignnevercreates\n\n  defines program\n\n    AssignNeverCreatesDemo()\n      stdout <- Stdout()\n\n      // <- creates the variable\n      count <- 10\n      stdout.println(`Created: ${count}`)\n\n      // := updates the existing variable\n      count := 20\n      stdout.println(`Updated: ${count}`)\n\n      // Another example\n      message <- \"hello\"\n      message := \"goodbye\"\n      stdout.println(message)","migrationContext":"Python := (walrus) creates and assigns in expressions. Go := creates new variables. EK9 := does neither — it ONLY updates existing variables. Use <- to create.","keywords":["assign","create","declare","error","go","python","walrus"],"primaryTopics":[":= never creates","declaration vs assignment"],"typicalErrors":[{"error":"E50001","correct":"      count <- 10","incorrect":"      count := 10","explanation":"The := operator requires the variable to already exist. Use <- to create a new variable. In EK9, := never creates — it only reassigns."}],"companions":[]}
{"id":1041,"category":"Getting Started","question":"I come from Python. I keep using := where I should use <-. How do I remember the difference?","url":"https://ek9.io/qa/QA1041.html","alternatePhrasings":["As a Python developer, how do I stop confusing := and <- in EK9?","Python habit: I write := for everything. What is the EK9 rule?","Help me break the Python := habit in EK9"],"answer":"The Python habit to break: in Python, := (walrus) creates a variable inside an expression. In EK9, := NEVER creates anything.\n\nSimple rule: first time you mention a name, use <-. Every time after, use :=.\n\n  name <- \"Alice\"       FIRST mention: <- to create\n  name := \"Bob\"         SECOND mention: := to update\n\nThink of <- as 'born here' and := as 'changed here'.\n\nPython patterns mapped to EK9:\n\n  Python: x = 10              EK9: x <- 10        (first use)\n  Python: x = 20              EK9: x := 20        (update)\n  Python: if (m := re.match)  EK9: if m <- match() (guard creates m)\n  Python: [x := f(y)]         EK9: not needed — use stream pipeline\n\nThe compiler catches every mistake. If you use := on a name that does not exist, the compiler tells you. If you use <- on a name that already exists, the compiler tells you. You cannot get it wrong without knowing.\n\nSee Q1040 for the general rule. See Q986 for walrus vs EK9 assign. See Q1039 for <- vs :=? distinction.","ek9Example":"defines module qa.gettingstarted.pythonassigntrap\n\n  defines function\n\n    findMatch() as pure\n      -> text as String\n      <- rtn as String: String()\n\n      minLength <- 3\n      if text.length() > minLength\n        rtn: text\n\n  defines program\n\n    PythonTrapDemo()\n      stdout <- Stdout()\n\n      // <- creates (like Python's first x = ...)\n      greeting <- \"Hello\"\n      stdout.println(greeting)\n\n      // := updates (like Python's second x = ...)\n      greeting := \"Hi there\"\n      stdout.println(greeting)\n\n      // <- in guard (replaces Python's walrus := in if)\n      if match <- findMatch(\"EK9 language\")\n        stdout.println(`Found: ${match}`)","migrationContext":"Python developers: forget := walrus habits. EK9 <- is for creation, := is for update. The compiler enforces this — you cannot confuse them without an error.","keywords":["assign","declare","habit","migrate","python","trap","walrus"],"primaryTopics":["Python := migration","breaking walrus habit"],"typicalErrors":[{"error":"E50001","correct":"greeting <- \"Hello\"","incorrect":"greeting := \"Hello\"","explanation":"In EK9 ':=' only updates an existing variable; use '<-' the first time you introduce a name, or it is not resolved. See ek9 -h E50001 for details."}],"companions":[]}
{"id":1042,"category":"Getting Started","question":"I come from Go. How does EK9 assignment differ from Go's := and =?","url":"https://ek9.io/qa/QA1042.html","alternatePhrasings":["As a Go developer, what are the EK9 equivalents of := and =?","Go uses := to declare. What does EK9 use?","Map Go's short variable declaration to EK9 syntax"],"answer":"Go and EK9 both separate declaration from assignment, but they use OPPOSITE symbols.\n\nGo:  := declares (creates)    = assigns (updates)\nEK9: <- declares (creates)    := assigns (updates)\n\nGo's := is EK9's <-\nGo's = is EK9's :=\n\nExamples:\n  Go:  name := \"Alice\"        EK9: name <- \"Alice\"\n  Go:  name = \"Bob\"           EK9: name := \"Bob\"\n  Go:  count := 0             EK9: count <- 0\n  Go:  count = count + 1      EK9: count := count + 1\n\nThe key difference: in Go, := can also reassign in multi-return contexts. In EK9, <- ONLY creates. It never reassigns. The separation is absolute.\n\nAnother difference: EK9 has :=? (guarded assignment) which Go does not have. It only assigns if the target is currently unset.","ek9Example":"defines module qa.gettingstarted.goassigntrap\n\n  defines program\n\n    GoTrapDemo()\n      stdout <- Stdout()\n\n      // Go: name := \"Alice\"  →  EK9: name <- \"Alice\"\n      name <- \"Alice\"\n      stdout.println(name)\n\n      // Go: name = \"Bob\"  →  EK9: name := \"Bob\"\n      name := \"Bob\"\n      stdout.println(name)\n\n      // Go: count := 0  →  EK9: count <- 0\n      count <- 0\n      count := count + 1\n      stdout.println(`Count: ${count}`)","migrationContext":"Go := declares, EK9 <- declares. Go = assigns, EK9 := assigns. The symbols are swapped. EK9 also adds :=? for conditional assignment which Go lacks.","keywords":["assign","declare","go","golang","migrate","short","variable"],"primaryTopics":["Go := migration","symbol mapping"],"typicalErrors":[{"error":"E50001","correct":"      count <- 0","incorrect":"      count := 0","explanation":"Go developers: your := habit means 'declare' but in EK9, := means 'update'. Swap to <- for declarations."}],"companions":[]}
{"id":1043,"category":"Debugging and Troubleshooting","question":"I get E05030 'not open to be extended'. What does this mean and how do I fix it?","url":"https://ek9.io/qa/QA1043.html","alternatePhrasings":["Diagnose E05030: my class cannot be extended","The compiler says my type is not open. What is going on?","Why can't I extend this class? I get E05030"],"answer":"E05030 means you tried to extend a type that is closed. In EK9, ALL types are closed by default — they cannot be extended unless explicitly marked 'as open'.\n\nDiagnosis steps:\n1. Find the class you are trying to extend\n2. Check if it has 'as open' in its declaration\n3. If it does not, it is closed and cannot be extended\n\nFix option 1 — make the parent open (if you own it):\n  BEFORE: MyBase\n  AFTER:  MyBase as open\n\nFix option 2 — use composition instead (recommended):\n  BEFORE: MyChild extends MyBase\n  AFTER:  MyChild\n            delegate as MyBase?\n\nComposition is the preferred pattern in EK9. It avoids tight coupling and works with all types, including built-in types like List and Dict which are always closed.\n\nBuilt-in types (List, Dict, Optional, Result) can NEVER be extended. Always use composition with these.\n\nRun 'ek9 -h E05030' for the full compiler explanation.\n\nSee Q1044 for extending built-in types. See Q102 for the 'as open' modifier. See Q109 for composition patterns. See Q1027 for a design review using composition.","ek9Example":"defines module qa.debugging.diagnosee05030\n\n  defines class\n\n    //This class is open — it CAN be extended\n    Vehicle as open\n      speed <- Float()\n\n      default Vehicle()\n\n      accelerate()\n        -> amount as Float\n        speed :=? amount\n\n      override operator ? as pure\n        <- rtn as Boolean: speed?\n\n      operator $ as pure\n        <- rtn as String: \"Vehicle\"\n\n    //This extends Vehicle — allowed because Vehicle is open\n    Car is Vehicle\n      brand as String?\n\n      default private Car()\n\n      Car()\n        -> brand as String\n        this.brand: brand\n\n      override operator ? as pure\n        <- rtn as Boolean: brand?\n\n      override operator $ as pure\n        <- rtn as String: `${brand} car`\n\n  defines program\n\n    DiagnoseE05030Demo()\n      stdout <- Stdout()\n\n      car <- Car(\"Ford\")\n      car.accelerate(60.0)\n      stdout.println(`${car}`)","migrationContext":"Java classes are open by default. EK9 classes are closed by default. Use 'as open' to opt in, or use composition instead.","keywords":["E05030","closed","diagnose","error","extend","investigate","open"],"primaryTopics":["E05030 diagnosis","closed type error"],"typicalErrors":[{"error":"E05030","correct":"Vehicle as open","incorrect":"Vehicle","explanation":"Removing 'as open' makes Vehicle a closed type (EK9 types are closed by default), so 'Car is Vehicle' is rejected as not open to be extended. See ek9 -h E05030 for details."}],"companions":[]}
{"id":1044,"category":"Debugging and Troubleshooting","question":"I tried to extend List and got E05030. How do I wrap a collection instead?","url":"https://ek9.io/qa/QA1044.html","alternatePhrasings":["Why can't I extend List of String in EK9?","How do I create a custom list type without inheritance?","Diagnose E05030 on a built-in generic type"],"answer":"Built-in generic types (List, Dict, Optional, Result, PriorityQueue, MutexLock, DictEntry) are permanently closed. They cannot be extended. This is by design — extending collections mixes container behaviour with application logic.\n\nThe fix is composition: hold the collection as a property and expose only the operations you need.\n\nBEFORE (E05030 error):\n  UniqueNames extends List of String   closed — cannot extend\n\nAFTER (composition):\n  UniqueNames\n    items as List of String?\n\n    add()\n      -> name as String\n      if not items.contains(name)\n        items += name\n\n    count() as pure\n      <- rtn as Integer: items.length()\n\nBenefits of composition:\n- You control the API — only expose what makes sense\n- The internal collection type can change without affecting callers\n- Works with any built-in type, not just open ones\n- Your type gets its own operators (?, $, etc.)\n\nRun 'ek9 -h E05030' for the compiler explanation.\n\nSee Q1043 for E05030 on user-defined types. See Q128 for why collections are closed. See Q212 for composition over inheritance.","ek9Example":"defines module qa.debugging.diagnoseclosedbuiltin\n\n  defines class\n\n    UniqueNames\n      items as List of String?\n\n      UniqueNames()\n        items: List() of String\n\n      add()\n        -> name as String\n        if not items.contains(name)\n          items += name\n\n      count() as pure\n        <- rtn as Integer: items.length()\n\n      override operator ? as pure\n        <- rtn as Boolean: items?\n\n      operator $ as pure\n        <- rtn as String: `UniqueNames(${count()} items)`\n\n  defines program\n\n    ClosedBuiltinDemo()\n      stdout <- Stdout()\n\n      names <- UniqueNames()\n      names.add(\"Alice\")\n      names.add(\"Bob\")\n      names.add(\"Alice\")\n      stdout.println(`${names}`)","migrationContext":"Java allows extending ArrayList. EK9 collections are closed. Use composition to create custom collection wrappers.","keywords":["E05030","builtin","closed","composition","extend","list","wrap"],"primaryTopics":["E05030 built-in types","collection composition"],"typicalErrors":[{"error":"E05030","correct":"    UniqueNames\n      items as List of String?","incorrect":"    UniqueNames extends List of String","explanation":"List is a closed built-in type. Use composition: hold a List property and delegate methods to it."}],"companions":[]}
{"id":1045,"category":"Code Quality","question":"Review this design. Should it use composition instead of inheritance?","url":"https://ek9.io/qa/QA1045.html","alternatePhrasings":["Is inheritance the right choice here or should I use composition?","Assess whether this class hierarchy is appropriate","When should I use composition over inheritance in EK9?"],"answer":"In EK9, composition is generally preferred over inheritance. Types are closed by default — you must explicitly opt into extension with 'as open'.\n\nWHEN INHERITANCE IS APPROPRIATE:\n- True 'is-a' relationship (Circle is a Shape)\n- Shared behaviour that varies by subtype (abstract methods)\n- Small, stable hierarchies (2-3 levels max)\n\nWHEN COMPOSITION IS BETTER:\n- 'Has-a' relationship (Car has an Engine, not Car is an Engine)\n- Combining capabilities from multiple sources (use traits)\n- When the parent class might change (composition isolates you)\n- When you need to swap implementations at runtime\n\nEK9 COMPOSITION TOOLS:\n- Traits for shared contracts: 'with trait of Printable'\n- Delegation with 'by' keyword\n- Records for pure data aggregation\n- Components for DI-managed services\n\nThe compiler enforces max inheritance depth — if you hit the limit, it is a signal to refactor toward composition.","ek9Example":"defines module qa.codequality.reviewcomposition\n\n  defines trait\n\n    Describable\n      describe() as pure\n        <- rtn as String?\n\n  defines class\n\n    Engine\n      horsePower as Integer: 0\n\n      Engine()\n        -> horsePower as Integer\n        this.horsePower :=: horsePower\n\n      horsePower() as pure\n        <- rtn as Integer: horsePower\n\n      default operator\n\n    //Composition: Car HAS an Engine (correct)\n    Car with trait of Describable\n      engine as Engine: Engine(0)\n      modelName as String: String()\n\n      Car()\n        ->\n          modelName as String\n          engine as Engine\n        this.modelName :=: modelName\n        this.engine :=: engine\n\n      override describe() as pure\n        <- rtn as String: `${modelName} (${engine.horsePower()}hp)`\n\n      default operator\n\n  defines program\n\n    CompositionDemo()\n      stdout <- Stdout()\n\n      engine <- Engine(250)\n      car <- Car(\"Roadster\", engine)\n      stdout.println(car.describe())","migrationContext":"EK9 encourages composition through closed types, traits, and delegation. Inheritance is available but not the default approach.","keywords":["composition","delegation","design","inheritance","review","trait"],"primaryTopics":["composition vs inheritance review","design assessment"],"typicalErrors":[{"error":"E05030","correct":"  defines class\n\n    Engine","incorrect":"      defines class\n        Car extends Engine","explanation":"Car is not an Engine — it has an Engine. Use composition (field) not inheritance (extends). Also, Engine must be 'as open' to extend."}],"companions":[]}
{"id":1046,"category":"Code Quality","question":"Review this EK9 class for naming quality. Are the names descriptive enough?","url":"https://ek9.io/qa/QA1046.html","alternatePhrasings":["Assess the naming in this EK9 code","Is this class well-named by EK9 standards?","What naming improvements would you suggest for this code?"],"answer":"Review findings:\n\nGOOD NAMING:\n- Class name 'OrderProcessor' describes what it does\n- Method 'calculateTotal' clearly states its purpose\n- Field 'customerName' is descriptive\n\nCOULD BE IMPROVED:\n- 'items' is acceptable but 'orderItems' would be more specific in this context\n- 'process()' is vague — what does processing mean? Consider 'validateAndSubmit()' or 'fulfil()'\n- Parameter 'disc' should be 'discountPercentage' — abbreviations lose meaning over time\n\nEK9 NAMING PRINCIPLES:\n- Types use PascalCase, variables use camelCase\n- Names should describe purpose, not type (orderCount not intCount)\n- The compiler bans generic names like temp, data, flag, value\n- Methods should describe the action: calculateTotal not doCalc","ek9Example":"defines module qa.codequality.reviewnaming\n\n  defines class\n\n    OrderProcessor\n      customerName as String: String()\n      orderItems as List of Float: List() of Float\n\n      OrderProcessor()\n        ->\n          customerName as String\n          orderItems as List of Float\n        this.customerName :=: customerName\n        this.orderItems :=: orderItems\n\n      calculateTotal() as pure\n        <- rtn as Float: 0.0\n\n        for orderItem in orderItems\n          rtn := rtn + orderItem\n\n      default operator\n\n  defines program\n\n    ReviewNamingDemo()\n      stdout <- Stdout()\n\n      orders <- [29.99, 15.50, 42.00]\n      processor <- OrderProcessor(\"Alice\", orders)\n      stdout.println(`Total: ${processor.calculateTotal()}`)","migrationContext":"EK9 enforces naming quality at compile time. Generic names cause compile errors, not just style warnings.","keywords":["assess","descriptive","naming","quality","review"],"primaryTopics":["code review naming","naming quality assessment"],"typicalErrors":[{"error":"E11031","correct":"      orders <- [29.99, 15.50, 42.00]\n      processor <- OrderProcessor(\"Alice\", orders)","incorrect":"      data <- [29.99, 15.50, 42.00]\n      processor <- OrderProcessor(\"Alice\", data)","explanation":"EK9 bans non-descriptive variable names like 'data' at compile time — use a name that describes what the value represents. See ek9 -h E11031 for details."}],"companions":[]}
{"id":1047,"category":"Operators and Expressions","question":"How do I use the #? hashcode operator on a record?","url":"https://ek9.io/qa/QA1047.html","alternatePhrasings":["How does #? work on records in EK9?","Show me hashcode with a record type","Can records have a hashcode operator?"],"answer":"Records can use 'default operator' to auto-generate #? from all fields. The generated hashcode combines the hash values of every field.\n\n  defines record\n    Coordinate\n      latitude as Float: 0.0\n      longitude as Float: 0.0\n      default operator\n\nUsage:\n  point <- Coordinate(51.5, -0.12)\n  hash <- #? point\n\nThe default operator generates #? that combines #?latitude and #?longitude. You can also define it manually if you need custom logic:\n\n  operator #? as pure\n    <- rtn as Integer: #?latitude + #?longitude\n\nThe #? operator always returns Integer. It must be pure. Records with default operator also get ==, $, :=:, and other operators automatically.\n\nSee Q911 for what #? means. See Q1053 for manual #? on a class. See Q1055 for default vs manual operators. See Q116 for what default operator generates.","ek9Example":"defines module qa.operators.hashcoderecord\n\n  defines record\n\n    Coordinate\n      latitude as Float: 0.0\n      longitude as Float: 0.0\n\n      Coordinate()\n        ->\n          latitude as Float\n          longitude as Float\n        this.latitude :=: latitude\n        this.longitude :=: longitude\n\n      default operator\n\n  defines program\n\n    HashcodeRecordDemo()\n      stdout <- Stdout()\n\n      pointA <- Coordinate(51.5, -0.12)\n      pointB <- Coordinate(51.5, -0.12)\n      pointC <- Coordinate(48.8, 2.35)\n\n      hashA <- #? pointA\n      hashB <- #? pointB\n      hashC <- #? pointC\n\n      stdout.println(`A hash: ${hashA}`)\n      stdout.println(`B hash: ${hashB}`)\n      stdout.println(`C hash: ${hashC}`)\n      stdout.println(`A == B: ${pointA == pointB}`)\n      stdout.println(`A == C: ${pointA == pointC}`)","migrationContext":"Java: records auto-generate hashCode(). Python: dataclasses can auto-generate __hash__(). EK9: 'default operator' on records generates #? from all fields.","keywords":["default","hashcode","integer","operator","record"],"primaryTopics":["#? on records","default operator hashcode"],"typicalErrors":[],"companions":[]}
{"id":1048,"category":"Operators and Expressions","question":"How do I use the #^ promote operator on a class?","url":"https://ek9.io/qa/QA1048.html","alternatePhrasings":["How does #^ type promotion work on a class?","Show me the promote operator on a custom class","How do I widen a class type to a simpler type?"],"answer":"Define operator #^ on your class to convert it to a wider or simpler type. The return type MUST be different from the class.\n\n  defines class\n    Percentage\n      amount as Float: 0.0\n\n      operator #^ as pure\n        <- rtn as Float: amount\n\nUsage:\n  discount <- Percentage(15.0)\n  asFloat <- #^ discount\n\nThe compiler uses #^ automatically when a Percentage is used where a Float is expected. This is type-safe widening — the compiler knows exactly how to convert.\n\nCommon patterns:\n- Percentage to Float (domain type to primitive)\n- Measurement to Float (unit type to raw number)\n- UserId to Integer (wrapper to underlying value)\n\nThe #^ operator must be pure, take no parameters, and return a different type. The compiler enforces this with E07420.\n\nSee Q912 for what #^ means. See Q1052 for #^ on a record. See Q839 for promote return type rules.","ek9Example":"defines module qa.operators.promoteclass\n\n  defines class\n\n    Percentage\n      amount as Float: 0.0\n\n      Percentage() as pure\n        -> amount as Float\n        this.amount :=: amount\n\n      operator #^ as pure\n        <- rtn as Float: amount\n\n      override operator ? as pure\n        <- rtn as Boolean: amount?\n\n      operator $ as pure\n        <- rtn as String: `${amount}%`\n\n  defines function\n\n    applyDiscount() as pure\n      ->\n        price as Float\n        rate as Float\n      <- rtn as Float: price - (price * rate)\n\n  defines program\n\n    PromoteClassDemo()\n      stdout <- Stdout()\n\n      discount <- Percentage(15.0)\n      stdout.println(`Discount: ${discount}`)\n\n      //Promote to Float for arithmetic\n      rawRate <- #^ discount\n      price <- 100.0\n      finalPrice <- applyDiscount(price, rawRate)\n      stdout.println(`Final: ${finalPrice}`)","migrationContext":"Java: implicit primitive widening (int to double). Rust: explicit 'as' casting. EK9: #^ operator provides explicit, type-safe promotion.","keywords":["class","conversion","operator","promote","widening"],"primaryTopics":["#^ on classes","type promotion"],"typicalErrors":[{"error":"E07420","correct":"      operator #^ as pure\n        <- rtn as Float: amount","incorrect":"      operator #^ as pure\n        <- rtn as Percentage: Percentage(amount)","explanation":"The #^ promote operator must return a DIFFERENT type. Returning the same type defeats the purpose and triggers E07420."}],"companions":[]}
{"id":1049,"category":"Operators and Expressions","question":"How do I implement comparison operators on a record for sorting?","url":"https://ek9.io/qa/QA1049.html","alternatePhrasings":["How do <, >, <=, >= work on a record?","Show me comparison operators on a custom record","How do I make a record sortable in EK9?"],"answer":"Implement the <=> (spaceship) operator on your record. Then derive <, >, <=, >= from it. All comparison operators must be pure.\n\n  defines record\n    Priority\n      level as Integer: 0\n\n      operator <=> as pure\n        -> other as Priority\n        <- rtn as Integer: level <=> other.level\n\n      operator < as pure\n        -> other as Priority\n        <- rtn as Boolean: (this <=> other) < 0\n\n      operator > as pure\n        -> other as Priority\n        <- rtn as Boolean: (this <=> other) > 0\n\n      operator <= as pure\n        -> other as Priority\n        <- rtn as Boolean: (this <=> other) <= 0\n\n      operator >= as pure\n        -> other as Priority\n        <- rtn as Boolean: (this <=> other) >= 0\n\nOnce these are defined, you can sort lists and use coalescing operators:\n  smaller <- priorityA <? priorityB\n  larger <- priorityA >? priorityB\n\nThe coalescing operators <? and >? return the lesser or greater value. They work because the comparison operators are defined.\n\nSee Q1050 for coalescing operators on a class. See Q239 for comparison ordering. See Q1051 for operators on traits.","ek9Example":"defines module qa.operators.comparisonrecord\n\n  defines record\n\n    Priority\n      level as Integer: 0\n      label as String: String()\n\n      Priority()\n        ->\n          level as Integer\n          label as String\n        this.level :=: level\n        this.label :=: label\n\n      operator <=> as pure\n        -> other as Priority\n        <- rtn as Integer: level <=> other.level\n\n      operator < as pure\n        -> other as Priority\n        <- rtn as Boolean: (this <=> other) < 0\n\n      operator > as pure\n        -> other as Priority\n        <- rtn as Boolean: (this <=> other) > 0\n\n      operator <= as pure\n        -> other as Priority\n        <- rtn as Boolean: (this <=> other) <= 0\n\n      operator >= as pure\n        -> other as Priority\n        <- rtn as Boolean: (this <=> other) >= 0\n\n      default operator\n\n  defines program\n\n    ComparisonRecordDemo()\n      stdout <- Stdout()\n\n      high <- Priority(10, \"Critical\")\n      medium <- Priority(5, \"Normal\")\n      low <- Priority(1, \"Low\")\n\n      stdout.println(`High > Low: ${high > low}`)\n      stdout.println(`Medium <= High: ${medium <= high}`)\n\n      //Coalescing: get the lesser/greater\n      lesser <- high <? medium\n      greater <- low >? medium\n      stdout.println(`Lesser: ${lesser}`)\n      stdout.println(`Greater: ${greater}`)","migrationContext":"Java: implements Comparable. Python: __lt__, __gt__, etc. Rust: derive Ord. EK9: define <=> then derive <, >, <=, >= from it.","keywords":["comparison","operator","record","sort","spaceship"],"primaryTopics":["comparison operators on records","sortable records"],"typicalErrors":[{"error":"E07520","correct":"      operator < as pure\n        -> other as Priority\n        <- rtn as Boolean: (this <=> other) < 0","incorrect":"      operator < as pure\n        -> other as Priority\n        <- rtn as Integer: level - other.level","explanation":"The < operator must return Boolean, not Integer. Use <=> for Integer comparison results, < for Boolean."}],"companions":[],"oracleToolHint":{"tool":"ek9_add_member","intent":"operators","description":"Oracle can add comparison operators to a record for sorting support."}}
{"id":1050,"category":"Operators and Expressions","question":"How do the coalescing operators <?, >?, <=?, >=? work on a custom class?","url":"https://ek9.io/qa/QA1050.html","alternatePhrasings":["What are the comparison coalescing operators in EK9?","Show me <? and >? on a class with examples","How do I find the minimum or maximum of two objects?"],"answer":"The coalescing operators return one of the two operands based on comparison:\n\n  <? returns the lesser value (minimum)\n  >? returns the greater value (maximum)\n  <=? returns the left if left <= right, else right\n  >=? returns the left if left >= right, else right\n\nTo use these on a custom class, the class must implement the corresponding comparison operators (<, >, <=, >=).\n\n  score1 <- Score(85)\n  score2 <- Score(92)\n  minimum <- score1 <? score2     returns Score(85)\n  maximum <- score1 >? score2     returns Score(92)\n\nThey also handle unset values safely — if either operand is unset, the result reflects that.\n\nThese replace patterns like:\n  if a < b then result := a else result := b\nWith just:\n  result <- a <? b\n\nSee Q963 for <? in detail. See Q964 for >? in detail. See Q1049 for comparison operators on records. See Q243 for coalescing with unset values.","ek9Example":"defines module qa.operators.coalescingclass\n\n  defines class\n\n    Score\n      points as Integer: 0\n\n      Score() as pure\n        -> points as Integer\n        this.points :=: points\n\n      operator <=> as pure\n        -> other as Score\n        <- rtn as Integer: points <=> other.points\n\n      operator < as pure\n        -> other as Score\n        <- rtn as Boolean: (this <=> other) < 0\n\n      operator > as pure\n        -> other as Score\n        <- rtn as Boolean: (this <=> other) > 0\n\n      operator <= as pure\n        -> other as Score\n        <- rtn as Boolean: (this <=> other) <= 0\n\n      operator >= as pure\n        -> other as Score\n        <- rtn as Boolean: (this <=> other) >= 0\n\n      operator == as pure\n        -> other as Score\n        <- rtn as Boolean: points == other.points\n\n      operator #? as pure\n        <- rtn as Integer: #? points\n\n      override operator ? as pure\n        <- rtn as Boolean: points?\n\n      operator $ as pure\n        <- rtn as String: `Score(${points})`\n\n  defines program\n\n    CoalescingClassDemo()\n      stdout <- Stdout()\n\n      alice <- Score(85)\n      bob <- Score(92)\n      charlie <- Score(78)\n\n      //Coalescing: find minimum and maximum\n      minimum <- alice <? bob\n      maximum <- alice >? bob\n      stdout.println(`Min: ${minimum}`)\n      stdout.println(`Max: ${maximum}`)\n\n      //Chain coalescing for three values\n      lowest <- alice <? bob <? charlie\n      highest <- alice >? bob >? charlie\n      stdout.println(`Lowest: ${lowest}`)\n      stdout.println(`Highest: ${highest}`)","migrationContext":"Java: Math.min(a,b) / Math.max(a,b). Python: min(a,b) / max(a,b). Rust: a.min(b) / a.max(b). EK9: a <? b / a >? b as operators.","keywords":["class","coalescing","compare","maximum","minimum","operator"],"primaryTopics":["coalescing operators","<? >? <=? >=?"],"typicalErrors":[{"error":"E50001","correct":"minimum <- alice <? bob","incorrect":"minimum <- Math.min(alice, bob)","explanation":"EK9 has no Math class; use the '<?' coalescing operator to get the lesser of two values. See ek9 -h E50001 for details."}],"companions":[]}
{"id":1051,"category":"Operators and Expressions","question":"Can a trait define comparison and hashcode operators in EK9?","url":"https://ek9.io/qa/QA1051.html","alternatePhrasings":["Show me operator definitions inside a trait","Do traits support #? and comparison operators?","Define a sortable contract using a trait with operators"],"answer":"Yes, traits can define operator signatures that implementing classes must provide. A trait can declare abstract operators or provide default implementations.\n\nAbstract operator in a trait (no body):\n  defines trait\n    Rankable\n      operator <=> as pure\n        -> other as Rankable\n        <- rtn as Integer?\n\nDefault operator in a trait (with body):\n  defines trait\n    Identifiable\n      operator #? as pure\n        <- rtn as Integer: 0\n\nAny class 'with trait of Rankable' must implement <=>. The trait contract guarantees that all Rankable objects can be compared.\n\nThis pattern is useful for:\n- Defining sortable contracts\n- Ensuring all types in a collection support comparison\n- Building generic algorithms that depend on ordering\n\nNote: override is required when implementing trait operators in a class.\n\nSee Q1049 for comparison operators on records. See Q1050 for coalescing on classes. See Q210 for trait delegation.","ek9Example":"defines module qa.operators.operatorsontrait\n\n  defines trait\n\n    Rankable\n      rank() as pure\n        <- rtn as Integer?\n\n  defines class\n\n    Task with trait of Rankable\n      priority as Integer: 0\n      taskName as String: String()\n\n      Task()\n        ->\n          taskName as String\n          priority as Integer\n        this.taskName :=: taskName\n        this.priority :=: priority\n\n      override rank() as pure\n        <- rtn as Integer: priority\n\n      operator <=> as pure\n        -> other as Task\n        <- rtn as Integer: priority <=> other.priority\n\n      operator < as pure\n        -> other as Task\n        <- rtn as Boolean: (this <=> other) < 0\n\n      operator > as pure\n        -> other as Task\n        <- rtn as Boolean: (this <=> other) > 0\n\n      operator #? as pure\n        <- rtn as Integer: #? priority\n\n      override operator ? as pure\n        <- rtn as Boolean: taskName? and priority?\n\n      operator $ as pure\n        <- rtn as String: `${taskName}(${priority})`\n\n  defines program\n\n    TraitOperatorsDemo()\n      stdout <- Stdout()\n\n      taskA <- Task(\"Fix bug\", 3)\n      taskB <- Task(\"Deploy\", 8)\n\n      stdout.println(`A < B: ${taskA < taskB}`)\n      stdout.println(`A > B: ${taskA > taskB}`)\n      stdout.println(`A hash: ${#? taskA}`)\n\n      //Coalescing: get higher priority\n      urgent <- taskA >? taskB\n      stdout.println(`More urgent: ${urgent}`)","migrationContext":"Java: Comparable interface. Rust: Ord trait. Python: __lt__ protocol. EK9: trait with operator signatures.","keywords":["abstract","comparison","contract","hashcode","operator","trait"],"primaryTopics":["operators in traits","sortable contract"],"typicalErrors":[{"error":"E05120","correct":"override rank() as pure","incorrect":"rank() as pure","explanation":"Overriding an inherited trait member (here rank() from trait Rankable) requires the 'override' keyword, otherwise the compiler reports E05120. See ek9 -h E05120 for details."}],"companions":[]}
{"id":1052,"category":"Operators and Expressions","question":"Define #^ on a record to promote it to String for display purposes.","url":"https://ek9.io/qa/QA1052.html","alternatePhrasings":["How do I make a record automatically convert to String with #^?","Show a record with a promote operator that returns String","Use #^ to widen a record to its string representation"],"answer":"Define operator #^ on your record to return String. The compiler will use this automatically when a String is needed.\n\n  defines record\n    Colour\n      red as Integer: 0\n      green as Integer: 0\n      blue as Integer: 0\n\n      operator #^ as pure\n        <- rtn as String: `rgb(${red}, ${green}, ${blue})`\n\nUsage:\n  crimson <- Colour(220, 20, 60)\n  asString <- #^ crimson\n\nNote: #^ and $ serve different purposes.\n- $ is the string representation operator (called with $variable)\n- #^ is the promotion operator (automatic type widening)\n\nWhen a function expects a String parameter and you pass a Colour, the compiler calls #^ automatically. With $, you must explicitly write $colour.\n\nBoth are pure and take no parameters. The difference is intent: $ is for display, #^ is for type conversion.\n\nSee Q912 for what #^ means. See Q1048 for #^ on a class. See Q1055 for default vs manual operators.","ek9Example":"defines module qa.operators.promoterecordstring\n\n  defines record\n\n    Colour\n      red as Integer: 0\n      green as Integer: 0\n      blue as Integer: 0\n\n      Colour()\n        ->\n          red as Integer\n          green as Integer\n          blue as Integer\n        this.red :=: red\n        this.green :=: green\n        this.blue :=: blue\n\n      operator #^ as pure\n        <- rtn as String: `rgb(${red}, ${green}, ${blue})`\n\n      default operator\n\n  defines program\n\n    PromoteRecordDemo()\n      stdout <- Stdout()\n\n      crimson <- Colour(220, 20, 60)\n      skyBlue <- Colour(135, 206, 235)\n\n      //Explicit promote\n      asString <- #^ crimson\n      stdout.println(asString)\n\n      //$ for display\n      stdout.println(`Colour: ${skyBlue}`)","migrationContext":"Java: toString() for display, casting for conversion. EK9 separates these: $ for display, #^ for type promotion.","keywords":["convert","display","operator","promote","record","string"],"primaryTopics":["#^ on records","record to String promotion"],"typicalErrors":[{"error":"E07420","correct":"      operator #^ as pure\n        <- rtn as String: `rgb(${red}, ${green}, ${blue})`","incorrect":"      operator #^ as pure\n        <- rtn as Colour: Colour(red, green, blue)","explanation":"The #^ operator must return a different type. Returning the same record type triggers E07420."}],"companions":[]}
{"id":1053,"category":"Operators and Expressions","question":"Implement #? manually on a class that combines multiple fields.","url":"https://ek9.io/qa/QA1053.html","alternatePhrasings":["Write a custom hashcode operator for a class with several fields","How do I build a hashcode from multiple fields in EK9?","Show me a multi-field #? implementation on a class"],"answer":"Combine the hash values of each field using arithmetic. The #? prefix operator gets the hash of individual fields.\n\n  operator #? as pure\n    <- rtn as Integer: #?firstName + #?lastName + #?age\n\nEach #?field calls _hashcode() on that field. Adding them produces a combined hash. For better distribution, multiply by a prime:\n\n  operator #? as pure\n    <- rtn as Integer: (#?firstName * 31) + (#?lastName * 17) + #?age\n\nAlternatively, use 'default operator' to auto-generate #? from all fields. Manual implementation is only needed when you want a custom hash strategy (for example, hashing on a subset of fields).\n\nThe #? operator must be pure, take no parameters, and return Integer.\n\nSee Q911 for what #? means. See Q1047 for #? on records with default operator. See Q1055 for default vs manual operators.","ek9Example":"defines module qa.operators.hashcodeclassmulti\n\n  defines class\n\n    Employee\n      firstName as String: String()\n      lastName as String: String()\n      employeeId as Integer: 0\n\n      default private Employee()\n\n      Employee()\n        ->\n          firstName as String\n          lastName as String\n          employeeId as Integer\n        this.firstName :=: firstName\n        this.lastName :=: lastName\n        this.employeeId :=: employeeId\n\n      //Custom #? using subset of fields\n      operator #? as pure\n        <- rtn as Integer: (#?firstName * 31) + (#?lastName * 17) + #?employeeId\n\n      operator == as pure\n        -> other as Employee\n        <- rtn as Boolean: employeeId == other.employeeId\n\n      override operator ? as pure\n        <- rtn as Boolean: firstName? and lastName? and employeeId?\n\n      operator $ as pure\n        <- rtn as String: `${firstName} ${lastName} (${employeeId})`\n\n  defines program\n\n    HashcodeClassDemo()\n      stdout <- Stdout()\n\n      emp1 <- Employee(\"Alice\", \"Smith\", 1001)\n      emp2 <- Employee(\"Alice\", \"Smith\", 1001)\n      emp3 <- Employee(\"Bob\", \"Jones\", 1002)\n\n      stdout.println(`emp1 hash: ${#? emp1}`)\n      stdout.println(`emp2 hash: ${#? emp2}`)\n      stdout.println(`emp3 hash: ${#? emp3}`)\n      stdout.println(`emp1 == emp2: ${emp1 == emp2}`)","migrationContext":"Java: Objects.hash(field1, field2). Python: hash((f1, f2)). EK9: #?field1 + #?field2 or 'default operator'.","keywords":["class","custom","hashcode","manual","multi-field","operator"],"primaryTopics":["custom #? implementation","multi-field hashcode"],"typicalErrors":[{"error":"E07550","correct":"      operator #? as pure\n        <- rtn as Integer: (#?firstName * 31) + (#?lastName * 17) + #?employeeId","incorrect":"      operator #? as pure\n        <- rtn as Float: #?firstName + #?lastName","explanation":"The #? operator must return Integer. Using Float or any other type triggers E07550."}],"companions":[]}
{"id":1054,"category":"Getting Started","question":"My code has three errors. Fix them all with minimal changes.","url":"https://ek9.io/qa/QA1054.html","alternatePhrasings":["Fix these multiple errors in my EK9 code","Quick fix: several mistakes in one function","Correct all the problems in this code at once"],"answer":"Here are the three fixes:\n\n1. Change := to <- on line where greeting is first created.\n   greeting := \"Hello\"  becomes  greeting <- \"Hello\"\n   Reason: := updates existing variables. <- creates new ones.\n\n2. Add 'as pure' to the function that has no side effects.\n   formatName()  becomes  formatName() as pure\n   Reason: functions that only compute from inputs should be pure.\n\n3. Change the return variable type initialisation.\n   <- rtn as String?  becomes  <- rtn as String: String()\n   Reason: return values must always be initialised. String() creates an unset String.\n\nEach fix is one small change. Do not restructure the code or add features — just fix the errors.","ek9Example":"defines module qa.gettingstarted.multistepfix\n\n  defines function\n\n    formatName() as pure\n      -> name as String\n      <- rtn as String: String()\n\n      if name?\n        rtn := \"Dear \" + name\n\n  defines program\n\n    MultiStepFixDemo()\n      stdout <- Stdout()\n\n      greeting <- \"Hello\"\n      stdout.println(greeting)\n\n      formatted <- formatName(\"Alice\")\n      stdout.println(formatted)","migrationContext":"EK9 error fixing is incremental. Fix one error at a time. The compiler will guide you to the next one.","keywords":["errors","fix","generalist","minimal","multiple","quick"],"primaryTopics":["multi-step fixing","minimal corrections"],"typicalErrors":[{"error":"E50001","correct":"      greeting <- \"Hello\"","incorrect":"      greeting := \"Hello\"","explanation":"Use <- to create a new variable. The := operator expects the variable to already exist."}],"companions":[]}
{"id":1055,"category":"Operators and Expressions","question":"Should I write #? and $ manually or use default operator?","url":"https://ek9.io/qa/QA1055.html","alternatePhrasings":["When should I use default operator instead of writing operators by hand?","Show the difference between default operator and manual #? and $ implementations","Can I mix default operator with custom operator overrides?"],"answer":"Use 'default operator' when field-by-field behaviour is correct. Write manual operators only when you need custom logic.\n\nDEFAULT OPERATOR (recommended for most types):\n  defines record\n    Address\n      street as String: String()\n      city as String: String()\n      default operator\n\nThis generates #?, $, ==, <>, <=>, and ? automatically from all fields. The generated $ concatenates field strings. The generated #? combines field hashes.\n\nMANUAL OPERATORS (when you need custom behaviour):\n  defines class\n    Account\n      accountId as Integer: 0\n      balance as Float: 0.0\n\n      operator #? as pure\n        <- rtn as Integer: #? accountId\n\n      operator $ as pure\n        <- rtn as String: `Account-${accountId}`\n\nHere #? hashes only the accountId (not balance), and $ shows a formatted string. Default operator would hash both fields and show both in the string.\n\nYOU CAN MIX: use 'default operator' then override specific ones:\n  default operator\n  override operator $ as pure\n    <- rtn as String: `Custom: ${name}`\n\nThe default generates all operators, then your override replaces $.\n\nSee Q116 for what default operator generates. See Q1047 for #? on records. See Q1053 for manual #? on classes. See Q949 for default operator rules.","ek9Example":"defines module qa.operators.defaultvsmanual\n\n  defines record\n\n    //DEFAULT: all operators generated from fields\n    Address\n      street as String: String()\n      city as String: String()\n\n      Address()\n        ->\n          street as String\n          city as String\n        this.street :=: street\n        this.city :=: city\n\n      default operator\n\n  defines class\n\n    //MANUAL: custom hashcode and string\n    Account\n      accountId as Integer: 0\n      balance as Float: 0.0\n\n      default private Account()\n\n      Account()\n        ->\n          accountId as Integer\n          balance as Float\n        this.accountId :=: accountId\n        this.balance :=: balance\n\n      //Hash only by accountId, not balance\n      operator #? as pure\n        <- rtn as Integer: #? accountId\n\n      //Custom display format\n      operator $ as pure\n        <- rtn as String: `Account-${accountId}: ${balance}`\n\n      override operator ? as pure\n        <- rtn as Boolean: accountId? and balance?\n\n  defines program\n\n    DefaultVsManualDemo()\n      stdout <- Stdout()\n\n      //Record with default operator\n      addr <- Address(\"123 Main St\", \"London\")\n      stdout.println(`Address: ${addr}`)\n      stdout.println(`Hash: ${#? addr}`)\n\n      //Class with manual operators\n      acct <- Account(1001, 5250.75)\n      stdout.println(`${acct}`)\n      stdout.println(`Hash: ${#? acct}`)","migrationContext":"Java: records auto-generate, classes need manual. Python: @dataclass auto-generates. EK9: 'default operator' works on both records and classes.","keywords":["default","generate","hashcode","manual","operator","override","string"],"primaryTopics":["default vs manual operators","when to override"],"typicalErrors":[{"error":"E08180","correct":"      default operator","incorrect":"      operator == as pure\n        ...\n      operator $ as pure\n        ...\n      operator #? as pure\n        ...","explanation":"For simple value types, 'default operator' generates all standard operators in one line. Manual implementation is only needed for custom behaviour."}],"companions":[]}
{"id":1056,"category":"Operators and Expressions","question":"Is the $ operator the same as string interpolation in EK9?","url":"https://ek9.io/qa/QA1056.html","alternatePhrasings":["What is the difference between $variable and ${variable} in EK9?","Does $ mean interpolation in EK9?","How do I convert a value to String in EK9?"],"answer":"No. The $ operator and string interpolation are completely separate things in EK9.\n\n$ OPERATOR — converts any value to String:\n  age <- 25\n  ageAsString <- $age\nThis calls the _string() method on the value and returns a String. It works outside backtick strings as a standalone operator.\n\nSTRING INTERPOLATION — embeds values inside backtick strings:\n  message <- `Age is ${age}`\nThis is backtick string syntax. The ${...} is interpolation that happens inside backtick strings only.\n\nThe confusion:\n  $age outside backticks = calls _string() operator, returns String\n  ${age} inside backticks = interpolation, embeds value in string\n\nYou can combine them:\n  ${$myObject} inside backticks = explicitly calls _string() then interpolates\nBut usually ${myObject} is enough because interpolation calls _string() automatically.\n\nDefining $ on a custom type:\n  operator $ as pure\n    <- rtn as String: `value: ${amount}`\n\nThe $ operator must be pure, take no parameters, and return String.\n\nSee Q908 for $ operator details. See Q43 for backtick string escaping. See Q1055 for default vs manual operators.","ek9Example":"defines module qa.operators.dollarnotinterpolation\n\n  defines class\n\n    Temperature\n      celsius as Float: 0.0\n\n      Temperature() as pure\n        -> celsius as Float\n        this.celsius :=: celsius\n\n      //$ operator — converts to String\n      operator $ as pure\n        <- rtn as String: `${celsius}C`\n\n      override operator ? as pure\n        <- rtn as Boolean: celsius?\n\n  defines program\n\n    DollarDemo()\n      stdout <- Stdout()\n\n      reading <- Temperature(22.5)\n\n      //$ operator: converts to String\n      readingString <- $reading\n      stdout.println(readingString)\n\n      //Interpolation: embeds in backtick string\n      stdout.println(`The temperature is ${reading}`)\n\n      //Both together: explicit $ inside interpolation\n      stdout.println(`Explicit: ${$reading}`)","migrationContext":"Java: toString(). Python: str() / __str__. JavaScript: template literals use ${} for interpolation. EK9 separates these: $ is the conversion operator, ${} is backtick interpolation.","keywords":["backtick","conversion","dollar","interpolation","operator","string"],"primaryTopics":["$ vs interpolation","string conversion operator"],"typicalErrors":[{"error":"E50060","correct":"      readingString <- $reading","incorrect":"      readingString <- reading.toString()","explanation":"EK9 has no .toString() method; `reading.toString()` triggers E50060 (method not resolved) - use the $ prefix operator: $reading calls _string(). See ek9 -h E50060 for details."}],"companions":[]}
{"id":1057,"category":"Operators and Expressions","question":"Does ++ return a value in EK9? Can I write x <- y++?","url":"https://ek9.io/qa/QA1057.html","alternatePhrasings":["Is ++ a mutator or does it return a new value?","How do ++ and += work in EK9?","Can I use ++ in an expression in EK9?"],"answer":"In EK9, ++ and += are mutation operators. They modify the variable in place and return NOTHING. You cannot use them in expressions.\n\n++ INCREMENTS IN PLACE:\n  count <- 0\n  count++          modifies count to 1, returns nothing\n\n+= ADDS IN PLACE:\n  score <- 10\n  score += 5       modifies score to 15, returns nothing\n\nWHAT YOU CANNOT DO:\n  x <- count++     WRONG — ++ returns nothing, cannot assign\n  total <- a += b  WRONG — += returns nothing, cannot assign\n  if count++ > 5   WRONG — ++ returns nothing, cannot compare\n\nWHAT TO DO INSTEAD:\n  count++\n  x <- count       increment then read separately\n\nAll mutation operators return void: ++, --, +=, -=, *=, /=, :=:, :^:, :~:\n\nMutation operators CANNOT be marked 'as pure' because they change state. A pure function must not mutate.\n\nSee Q241 for all mutation operators. See Q908 for $ operator. See Q1055 for default vs manual operators.","ek9Example":"defines module qa.operators.incrementmutator\n\n  defines program\n\n    IncrementDemo()\n      stdout <- Stdout()\n\n      count <- 0\n      stdout.println(`Before: ${count}`)\n\n      //++ mutates in place, returns nothing\n      count++\n      stdout.println(`After ++: ${count}`)\n\n      //+= mutates in place, returns nothing\n      count += 10\n      stdout.println(`After += 10: ${count}`)\n\n      //-= also mutates in place\n      count -= 3\n      stdout.println(`After -= 3: ${count}`)","migrationContext":"Java/C/Go: ++ returns a value (pre/post increment). Python: no ++ operator. EK9: ++ mutates in place, returns nothing — cannot be used in expressions.","keywords":["expression","increment","mutator","operator","plus","void"],"primaryTopics":["++ is a mutator","+= returns nothing"],"typicalErrors":[{"error":"E07950","correct":"Before: ${count}","incorrect":"Before: ${count++}","explanation":"++ mutates in place and returns nothing, so it cannot appear inside an expression such as string interpolation; increment first, then read the variable. See ek9 -h E07950 for details."}],"companions":[]}
{"id":1058,"category":"Operators and Expressions","question":"What is the difference between :=: :^: and :~: in EK9?","url":"https://ek9.io/qa/QA1058.html","alternatePhrasings":["How do copy, replace, and merge operators work?","When should I use :=: vs :~: vs :^:?","Explain the three content transfer operators"],"answer":"EK9 has three operators for transferring content between objects:\n\n:=: COPY — copies all fields from source to target:\n  target :=: source\nEvery field in source is copied to target, overwriting all existing values. The default implementation does a shallow copy (field references are shared).\n\n:~: MERGE — copies only SET fields from source to target:\n  target :~: source\nOnly fields that are SET in source are copied. Unset fields in source leave the target fields unchanged. Use this for partial updates.\n\n:^: REPLACE — replaces the entire content:\n  target :^: source\nFully replaces the target content with the source. Similar to :=: but semantically means 'this object is now that object'.\n\nAll three are mutation operators — they modify the target in place and return nothing. They cannot be marked 'as pure'.\n\nCustom :~: example (merge only SET fields):\n  operator :~:\n    -> from as Config\n    if from.host?\n      host :=: from.host\n    if from.port?\n      port :=: from.port\n\nSee Q98 for record operators. See Q241 for all mutation operators. See Q116 for default operator generation.","ek9Example":"defines module qa.operators.copyreplacemerge\n\n  defines record\n\n    Config\n      host as String: String()\n      port as Integer: 0\n\n      Config()\n        ->\n          host as String\n          port as Integer\n        this.host :=: host\n        this.port :=: port\n\n      //:~: MERGE — only copies SET fields\n      operator :~:\n        -> from as Config\n        if from.host?\n          host :=: from.host\n        if from.port?\n          port :=: from.port\n\n      default operator\n\n  defines program\n\n    CopyReplaceMergeDemo()\n      stdout <- Stdout()\n\n      original <- Config(\"localhost\", 8080)\n      stdout.println(`Original: ${original}`)\n\n      // :=: COPY — all fields copied\n      backup <- Config(\"\", 0)\n      backup :=: original\n      stdout.println(`Copy: ${backup}`)\n\n      // :~: MERGE — only set fields from partial are copied\n      partial <- Config(\"remote.host\", 9090)\n      original :~: partial\n      stdout.println(`After merge: ${original}`)","migrationContext":"Java: clone() or copy constructor. Python: copy.copy() / copy.deepcopy(). Rust: Clone trait. EK9 has three distinct operators: :=: (copy all), :~: (merge set only), :^: (replace).","keywords":["copy","merge","mutation","operator","replace","transfer"],"primaryTopics":[":=: :^: :~: operators","copy vs merge vs replace"],"typicalErrors":[{"error":"E50060","correct":"      backup :=: original","incorrect":"      backup := original.clone()","explanation":"EK9 has no '.clone()' method; use ':=:' to copy all fields (':~:' merge, ':^:' replace). See ek9 -h E50060 for details."}],"companions":[]}
{"id":1059,"category":"Operators and Expressions","question":"Do the ?? and ?: operators check for null in EK9?","url":"https://ek9.io/qa/QA1059.html","alternatePhrasings":["Is ?? a null coalescing operator in EK9?","How do ?? and ?: work — are they null checks?","What do the coalescing operators actually check in EK9?"],"answer":"No. EK9 has no null. The ?? and ?: operators check whether a value is SET, not whether it is null.\n\n?? VALUE COALESCING:\n  result <- name ?? \"default\"\nReturns name if name is SET. Returns \"default\" if name is UNSET.\n\n?: ELVIS COALESCING:\n  result <- name ?: \"default\"\nSame behaviour as ?? — returns left if SET, otherwise right.\n\nThe distinction from other languages:\n  Java: name != null ? name : \"default\"     checks null\n  Kotlin: name ?: \"default\"                  checks null\n  EK9: name ?? \"default\"                     checks isSet\n\nIn EK9, a variable can exist but be UNSET. This is not the same as null. An unset String is a String object that has no meaningful value yet. It still exists — you can call ? on it to check.\n\n  name <- String()     name exists, but is UNSET\n  name?                returns false (unset)\n  name := \"Alice\"     now SET\n  name?                returns true\n\nIf BOTH sides of ?? are unset, the result is unset.\n\nSee Q899 for all coalescing operators. See Q898 for ? is not ternary. See Q883 for tri-state semantics.","ek9Example":"defines module qa.operators.coalescingisset\n\n  defines program\n\n    CoalescingDemo()\n      stdout <- Stdout()\n\n      //Unset string\n      name <- String()\n      stdout.println(`name is set: ${name?}`)\n\n      //?? returns right when left is unset\n      greeting <- name ?? \"stranger\"\n      stdout.println(`Hello ${greeting}`)\n\n      //Set the name\n      name := \"Alice\"\n      stdout.println(`name is set: ${name?}`)\n\n      //?? now returns left (it is set)\n      greeting := name ?? \"stranger\"\n      stdout.println(`Hello ${greeting}`)\n\n      //?: works the same way\n      title <- String()\n      displayTitle <- title ?: \"Untitled\"\n      stdout.println(`Title: ${displayTitle}`)","migrationContext":"Java: ternary with null check. Kotlin: ?: (elvis) checks null. JavaScript: ?? (nullish coalescing). EK9: ?? and ?: check isSet, not null — EK9 has no null.","keywords":["coalescing","elvis","isset","null","operator","unset"],"primaryTopics":["?? checks isSet not null","?: elvis operator"],"typicalErrors":[{"error":"E01073","correct":"      name := \"Alice\"","incorrect":"      name := null","explanation":"'null' does not exist in EK9 — a variable is either set or unset (tri-state), so assign a real value rather than 'null'. See ek9 -h E01073 for details."}],"companions":[]}
{"id":1060,"category":"Classes and OOP","question":"Are record fields public or private in EK9?","url":"https://ek9.io/qa/QA1060.html","alternatePhrasings":["Can I access record fields directly in EK9?","What is the difference between record and class field visibility?","Do records have getters and setters in EK9?"],"answer":"Record fields are PUBLIC. Class fields are PRIVATE. This is a fundamental difference.\n\nRECORD — fields are public, accessed directly:\n  defines record\n    Point\n      xCoord as Float: 0.0\n      yCoord as Float: 0.0\n\n  point <- Point(3.0, 4.0)\n  stdout.println($point.xCoord)    direct access — works\n\nCLASS — fields are private, accessed via methods:\n  defines class\n    Circle\n      radius as Float: 0.0\n\n  circle <- Circle(5.0)\n  circle.radius                    WRONG — private field\n\nWhy records are public:\n- Records are data carriers (like DTOs)\n- Direct field access is the intended usage\n- Functions operate on record data externally\n- No encapsulation needed for pure data\n\nWhy classes are private:\n- Classes encapsulate behaviour with data\n- Access is through methods and operators\n- Internal state is hidden\n\nRecords do NOT need getters or setters. Access fields directly. Use functions to operate on record data.\n\nSee Q97 for record basics. See Q98 for record operators. See Q93 for class basics.","ek9Example":"defines module qa.classesandoop.recordfieldspublic\n\n  defines record\n\n    Point\n      xCoord as Float: 0.0\n      yCoord as Float: 0.0\n\n      Point()\n        ->\n          xCoord as Float\n          yCoord as Float\n        this.xCoord :=: xCoord\n        this.yCoord :=: yCoord\n\n      default operator\n\n  defines function\n\n    distanceFromOrigin() as pure\n      -> point as Point\n      <- rtn as Float: point.xCoord + point.yCoord\n\n  defines program\n\n    RecordFieldsDemo()\n      stdout <- Stdout()\n\n      point <- Point(3.0, 4.0)\n\n      //Record fields are PUBLIC — direct access\n      stdout.println(`X: ${point.xCoord}`)\n      stdout.println(`Y: ${point.yCoord}`)\n\n      //Functions operate on public record data\n      dist <- distanceFromOrigin(point)\n      stdout.println(`Distance: ${dist}`)","migrationContext":"Java: record fields are effectively public (via accessors). Python: dataclass fields are public. Rust: struct fields configurable. EK9: record=public, class=private — always.","keywords":["access","class","field","private","public","record","visibility"],"primaryTopics":["record fields are public","class fields are private"],"typicalErrors":[{"error":"E50060","correct":"${point.xCoord}","incorrect":"${point.getX()}","explanation":"Records expose public fields accessed directly (point.xCoord); there are no getter methods, so point.getX() is an unresolved method. See ek9 -h E50060 for details."}],"companions":[]}
{"id":1061,"category":"Functions and Methods","question":"Write a reusable function that filters items above a threshold so I can call it from anywhere.","url":"https://ek9.io/qa/QA1061.html","alternatePhrasings":["Write a pure filter function I can reuse across my application","How do I write a function for use in a stream pipeline?","Create a named function I can pass to filter by"],"answer":"Define a named pure function with one parameter that returns Boolean. Then use it in any stream pipeline with 'filter by'.\n\n  defines function\n\n    isAboveThreshold() as pure\n      -> price as Float\n      <- rtn as Boolean?\n\n      minimumPrice <- 50.0\n      rtn: price > minimumPrice\n\nUsage in a pipeline:\n  cat prices | filter by isAboveThreshold | collect as List of Float\n\nThe function must:\n- Take exactly one parameter (the stream item type)\n- Return Boolean (true = keep, false = discard)\n- Be marked 'as pure' for use in pipelines\n\nFor a configurable threshold, extract the threshold into a record and use a method instead. See Q1064 for that pattern.\n\nSee Q1034 for stream map examples. See Q1035 for filter and collect.","ek9Example":"defines module qa.functions.writereusablefilter\n\n  defines function\n\n    isAboveThreshold() as pure\n      -> price as Float\n      <- rtn as Boolean?\n\n      minimumPrice <- 50.0\n      rtn: price > minimumPrice\n\n    formatPrice() as pure\n      -> price as Float\n      <- rtn as String: `Price: ${price}`\n\n  defines program\n\n    ReusableFilterDemo()\n      stdout <- Stdout()\n\n      prices <- [12.50, 75.00, 8.99, 120.00, 45.00, 89.99]\n\n      //Use the reusable function in a pipeline\n      expensive <- cat prices\n        | filter by isAboveThreshold\n        | collect as List of Float\n      stdout.println(`Expensive: ${expensive}`)\n\n      //Reuse the same function with different data\n      otherPrices <- [200.0, 30.0, 55.0]\n      otherExpensive <- cat otherPrices\n        | filter by isAboveThreshold\n        | collect as List of Float\n      stdout.println(`Other expensive: ${otherExpensive}`)\n\n      //Combine with map\n      cat prices\n        | filter by isAboveThreshold\n        | map with formatPrice\n        > stdout","migrationContext":"Java: Predicate<T> lambda. Python: lambda or def. EK9: named pure function with Boolean return.","keywords":["coder","filter","function","pipeline","pure","reusable","stream"],"primaryTopics":["write a filter function","reusable pipeline function"],"typicalErrors":[{"error":"E01010","correct":"    isAboveThreshold() as pure\n      -> price as Float\n      <- rtn as Boolean?","incorrect":"    isAboveThreshold = lambda price: price > 50","explanation":"EK9 has no lambda syntax. Define a named pure function that returns Boolean for use with 'filter by'."}],"companions":[]}
{"id":1062,"category":"Functions and Methods","question":"I need a simple one-off function to transform items in a stream pipeline.","url":"https://ek9.io/qa/QA1062.html","alternatePhrasings":["Write a small function for use with map in a pipeline","How do I write a transform function for stream processing?","Create a function that converts each item in a stream"],"answer":"Define a pure function that takes one parameter and returns the transformed value. Use it with 'map with' in a pipeline.\n\n  defines function\n\n    toUpperCase() as pure\n      -> name as String\n      <- rtn as String: name.upperCase()\n\nUsage:\n  cat names | map with toUpperCase > stdout\n\nFor a simple one-line transform, the function body can be on the return line:\n\n    addPrefix() as pure\n      -> name as String\n      <- rtn as String: \"Hello, \" + name\n\nThe function must:\n- Take exactly one parameter (the stream item type)\n- Return the transformed type (can be different from input)\n- Be marked 'as pure'\n\nSee Q1061 for filter functions. See Q1034 for chained transforms.","ek9Example":"defines module qa.functions.writepipelinefunction\n\n  defines function\n\n    toUpperCase() as pure\n      -> name as String\n      <- rtn as String: name.upperCase()\n\n    addPrefix() as pure\n      -> name as String\n      <- rtn as String: \"Hello, \" + name\n\n    extractLength() as pure\n      -> name as String\n      <- rtn as Integer: name.length()\n\n  defines program\n\n    PipelineFunctionDemo()\n      stdout <- Stdout()\n\n      names <- [\"alice\", \"bob\", \"charlie\"]\n\n      //Simple transform\n      cat names | map with toUpperCase > stdout\n\n      //Chain two transforms\n      cat names\n        | map with toUpperCase\n        | map with addPrefix\n        > stdout\n\n      //Transform to different type (String -> Integer)\n      lengths <- cat names\n        | map with extractLength\n        | collect as List of Integer\n      stdout.println(`Lengths: ${lengths}`)","migrationContext":"Java: Function<T,R> lambda. Python: lambda or def. EK9: named pure function with return type.","keywords":["coder","function","map","pipeline","simple","stream","transform"],"primaryTopics":["write a map function","stream transform function"],"typicalErrors":[{"error":"E01010","correct":"    toUpperCase() as pure\n      -> name as String\n      <- rtn as String: name.upperCase()","incorrect":"    cat names | map with (n -> n.upperCase())","explanation":"EK9 has no inline lambdas. Define a named pure function and pass it to 'map with' by name."}],"companions":[]}
{"id":1063,"category":"Functions and Methods","question":"Write a complete EK9 program that reads a list of scores, filters passing ones, and prints the results.","url":"https://ek9.io/qa/QA1063.html","alternatePhrasings":["Write a full EK9 program from scratch","Show me a complete working program with functions and a pipeline","Code a program that processes a list using filter and output"],"answer":"Here is a complete, compilable EK9 program:\n\ndefines module scores.processor\n\n  defines function\n\n    isPassing() as pure\n      -> score as Integer\n      <- rtn as Boolean?\n      passingMark <- 60\n      rtn: score >= passingMark\n\n    formatScore() as pure\n      -> score as Integer\n      <- rtn as String: `Score: ${score} - PASS`\n\n  defines program\n\n    ScoreProcessor()\n      stdout <- Stdout()\n      scores <- [85, 42, 91, 67, 73, 55, 88, 96, 61, 79]\n\n      cat scores\n        | filter by isPassing\n        | map with formatScore\n        > stdout\n\n      passing <- cat scores\n        | filter by isPassing\n        | collect as List of Integer\n      stdout.println(`Passing count: ${passing.length()}`)\n\nKey elements:\n- Module declaration at the top\n- Functions in a 'defines function' section\n- Program in a 'defines program' section\n- Pure functions for pipeline use\n- Stream pipeline for processing\n- collect as List for gathering results\n\nSee Q1061 for reusable filter functions. See Q1062 for transform functions.","ek9Example":"defines module scores.processor\n\n  defines function\n\n    isPassing() as pure\n      -> score as Integer\n      <- rtn as Boolean?\n\n      passingMark <- 60\n      rtn: score >= passingMark\n\n    formatScore() as pure\n      -> score as Integer\n      <- rtn as String: `Score: ${score} - PASS`\n\n  defines program\n\n    ScoreProcessor()\n      stdout <- Stdout()\n\n      scores <- [85, 42, 91, 67, 73, 55, 88, 96, 61, 79]\n\n      //Pipeline: filter passing, format, print\n      cat scores\n        | filter by isPassing\n        | map with formatScore\n        > stdout\n\n      //Collect passing scores\n      passing <- cat scores\n        | filter by isPassing\n        | collect as List of Integer\n      stdout.println(`Passing count: ${passing.length()}`)","migrationContext":"Java: class with main method, imports, System.out. Python: def + if __name__. EK9: module + defines function + defines program.","keywords":["code","coder","complete","filter","pipeline","program","write"],"primaryTopics":["write complete program","full EK9 program"],"typicalErrors":[{"error":"E01010","correct":"  defines function\n\n    isPassing() as pure\n      -> score as Integer\n      <- rtn as Boolean?","incorrect":"  def isPassing(score):\n    return score >= 60","explanation":"EK9 requires 'defines function' section header, type annotations on parameters, and <- for return declaration. No 'def' or 'return'."}],"companions":[]}
{"id":1064,"category":"Classes and OOP","question":"I have several functions that all take the same 4 arguments. How do I clean this up in EK9?","url":"https://ek9.io/qa/QA1064.html","alternatePhrasings":["How do I reduce repeated parameters across multiple functions?","Should I extract a record when many functions share the same arguments?","What is the idiomatic way to handle repeated function parameters in EK9?"],"answer":"Extract a record. Group the repeated parameters into a record type and pass that instead.\n\nBEFORE (repeated arguments):\n  calculateTotal() as pure\n    -> price as Float, quantity as Integer, discount as Float, taxRate as Float\n    <- rtn as Float: ...\n\n  formatInvoice() as pure\n    -> price as Float, quantity as Integer, discount as Float, taxRate as Float\n    <- rtn as String: ...\n\n  isValidOrder() as pure\n    -> price as Float, quantity as Integer, discount as Float, taxRate as Float\n    <- rtn as Boolean: ...\n\nAFTER (record parameter):\n  defines record\n    OrderLine\n      price as Float: 0.0\n      quantity as Integer: 0\n      discount as Float: 0.0\n      taxRate as Float: 0.0\n      default operator\n\n  calculateTotal() as pure\n    -> order as OrderLine\n    <- rtn as Float: ...\n\n  formatInvoice() as pure\n    -> order as OrderLine\n    <- rtn as String: ...\n\nBenefits:\n- One place to add a new field (not 4 function signatures)\n- Record fields are public so functions access them directly\n- Records get default operators (==, $, #?, etc.) for free\n- The record is a reusable type across your module\n\nSee Q97 for record basics. See Q1060 for record fields being public. See Q116 for default operator.","ek9Example":"defines module qa.classesandoop.extractrecord\n\n  defines record\n\n    OrderLine\n      price as Float: 0.0\n      quantity as Integer: 0\n      discount as Float: 0.0\n      taxRate as Float: 0.0\n\n      OrderLine()\n        ->\n          price as Float\n          quantity as Integer\n          discount as Float\n          taxRate as Float\n        this.price :=: price\n        this.quantity :=: quantity\n        this.discount :=: discount\n        this.taxRate :=: taxRate\n\n      default operator\n\n  defines function\n\n    calculateTotal() as pure\n      -> order as OrderLine\n      <- rtn as Float?\n\n      subtotal <- order.price * order.quantity\n      discounted <- subtotal - (subtotal * order.discount)\n      rtn: discounted + (discounted * order.taxRate)\n\n    formatInvoice() as pure\n      -> order as OrderLine\n      <- rtn as String: `${order.quantity} x ${order.price} = ${calculateTotal(order)}`\n\n    isValidOrder() as pure\n      -> order as OrderLine\n      <- rtn as Boolean: order.price? and order.quantity? and order.quantity > 0\n\n  defines program\n\n    ExtractRecordDemo()\n      stdout <- Stdout()\n\n      order <- OrderLine(price: 29.99, quantity: 3, discount: 0.10, taxRate: 0.20)\n\n      if isValidOrder(order)\n        stdout.println(formatInvoice(order))\n        stdout.println(`Total: ${calculateTotal(order)}`)","migrationContext":"Java: extract a parameter object or record. Python: use a dataclass. EK9: extract a record with default operator.","keywords":["arguments","extract","idiomatic","parameters","record","refactor","repeated"],"primaryTopics":["extract record from parameters","reduce repeated arguments"],"typicalErrors":[{"error":"E11031","correct":"    calculateTotal() as pure\n      -> order as OrderLine\n      <- rtn as Float?","incorrect":"    calculateTotal() as pure\n      -> p as Float, q as Integer, d as Float, t as Float\n      <- rtn as Float: p * q","explanation":"Single-letter parameter names hide their purpose. Extract a record with descriptive field names and pass that instead."}],"companions":[]}
{"id":1065,"category":"Functions and Methods","question":"Write an EK9 class with comparison operators so I can sort a list of them.","url":"https://ek9.io/qa/QA1065.html","alternatePhrasings":["How do I make a class sortable in EK9?","Write a class I can use in a sorted stream pipeline","Code a class with all comparison operators"],"answer":"Define the <=> (spaceship) operator, then derive <, >, <=, >= from it. Add #? for hashcode and $ for string display.\n\ndefines class\n  Product\n    productName as String: String()\n    price as Float: 0.0\n\n    Product()\n      -> productName as String, price as Float\n      this.productName :=: productName\n      this.price :=: price\n\n    operator <=> as pure\n      -> other as Product\n      <- rtn as Integer: price <=> other.price\n\n    operator < as pure\n      -> other as Product\n      <- rtn as Boolean: (this <=> other) < 0\n\n    operator > as pure\n      -> other as Product\n      <- rtn as Boolean: (this <=> other) > 0\n\n    operator $ as pure\n      <- rtn as String: `${productName}: ${price}`\n\nNow you can sort:\n  cat products | sort > stdout\n\nSee Q1049 for comparison on records. See Q1047 for hashcode. See Q1055 for default operator.","ek9Example":"defines module qa.functions.writesortableclass\n\n  defines class\n\n    Product\n      productName as String: String()\n      price as Float: 0.0\n\n      default private Product()\n\n      Product()\n        ->\n          productName as String\n          price as Float\n        this.productName :=: productName\n        this.price :=: price\n\n      operator <=> as pure\n        -> other as Product\n        <- rtn as Integer: price <=> other.price\n\n      operator < as pure\n        -> other as Product\n        <- rtn as Boolean: (this <=> other) < 0\n\n      operator > as pure\n        -> other as Product\n        <- rtn as Boolean: (this <=> other) > 0\n\n      operator <= as pure\n        -> other as Product\n        <- rtn as Boolean: (this <=> other) <= 0\n\n      operator >= as pure\n        -> other as Product\n        <- rtn as Boolean: (this <=> other) >= 0\n\n      operator #? as pure\n        <- rtn as Integer: #?productName + #?price\n\n      override operator ? as pure\n        <- rtn as Boolean: productName? and price?\n\n      operator $ as pure\n        <- rtn as String: `${productName}: ${price}`\n\n  defines program\n\n    SortableClassDemo()\n      stdout <- Stdout()\n\n      products <- [\n        Product(\"Widget\", 9.99),\n        Product(\"Gadget\", 24.99),\n        Product(\"Doohickey\", 4.99)\n      ]\n\n      //Sort by price (uses <=> operator)\n      stdout.println(\"Sorted by price:\")\n      cat products | sort > stdout\n\n      //Get cheapest\n      cheapest <- Product(\"Widget\", 9.99) <? Product(\"Gadget\", 24.99)\n      stdout.println(`Cheapest: ${cheapest}`)","migrationContext":"Java: implements Comparable<T>. Python: __lt__, __gt__. Rust: derive Ord. EK9: define <=> then derive comparison operators.","keywords":["class","code","coder","comparison","operator","sort","write"],"primaryTopics":["write sortable class","comparison operators"],"typicalErrors":[{"error":"E01010","correct":"      operator <=> as pure\n        -> other as Product\n        <- rtn as Integer: price <=> other.price","incorrect":"      implements Comparable<Product>","explanation":"EK9 has no 'implements' keyword for interfaces. Define operators directly on the class."}],"companions":[]}
{"id":1066,"category":"What AI Gets Wrong About EK9","question":"Does EK9 have list comprehensions like Python?","url":"https://ek9.io/qa/QA1066.html","alternatePhrasings":["Can I write [x*2 for x in items] in EK9?","Is there a list comprehension syntax in EK9?","How do I create a filtered list in one line in EK9?"],"answer":"No. EK9 has NO list comprehensions. This syntax does not exist:\n  [x*2 for x in items if x > 0]\n  [x for x in items when x > 0]\n\nThese are Python syntax. EK9 does not have them.\n\nUse a stream pipeline instead:\n  results <- cat items\n    | filter by isPositive\n    | map with doubleIt\n    | collect as List of Integer\n\nThe stream pipeline is more powerful than list comprehensions:\n- Named functions make the intent clear\n- Pipeline stages can be reordered easily\n- head, tail, sort, group, flatten are available\n- The result type is explicit\n\nDefine the helper functions:\n  isPositive() as pure\n    -> item as Integer\n    <- rtn as Boolean: item > 0\n\n  doubleIt() as pure\n    -> item as Integer\n    <- rtn as Integer: item * 2\n\nSee Q1061 for reusable filter functions. See Q1062 for transform functions. See Q1063 for a complete program.","ek9Example":"defines module qa.aimistakes.nolistcomprehensions\n\n  defines function\n\n    isPositive() as pure\n      -> item as Integer\n      <- rtn as Boolean: item > 0\n\n    doubleIt() as pure\n      -> item as Integer\n      <- rtn as Integer: item * 2\n\n  defines program\n\n    NoComprehensionDemo()\n      stdout <- Stdout()\n\n      items <- [-3, 5, -1, 8, 0, 12, -7, 3]\n\n      //Stream pipeline replaces list comprehension\n      results <- cat items\n        | filter by isPositive\n        | map with doubleIt\n        | collect as List of Integer\n      stdout.println(`Doubled positives: ${results}`)","migrationContext":"Python: [x*2 for x in items if x > 0]. EK9: cat items | filter by pred | map with fn | collect as Type. No list comprehensions exist in EK9.","keywords":["comprehension","list","mistake","pipeline","python","stream"],"primaryTopics":["no list comprehensions","use stream pipeline"],"typicalErrors":[{"error":"E01081","correct":"      results <- cat items\n        | filter by isPositive\n        | map with doubleIt\n        | collect as List of Integer","incorrect":"      results <- [x*2 for x in items if x > 0]","explanation":"EK9 has no list comprehension syntax. Use a stream pipeline: cat source | filter by fn | map with fn | collect as Type."}],"companions":[]}
{"id":1067,"category":"Operators and Expressions","question":"What is the difference between < and <? in EK9?","url":"https://ek9.io/qa/QA1067.html","alternatePhrasings":["How do < and <? differ in EK9?","Is <? the same as less-than in EK9?","Contrast the < comparison with the <? coalescing operator"],"answer":"They do completely different things:\n\n< COMPARES and returns Boolean:\n  isSmaller <- 5 < 10         returns true\n  isSmaller is a Boolean.\n\n<? COMPARES and returns the LESSER VALUE:\n  smaller <- 5 <? 10          returns 5\n  smaller is an Integer (same type as the operands).\n\nSide by side:\n  5 < 10    returns true      (Boolean: is 5 less than 10?)\n  5 <? 10   returns 5         (Integer: the lesser of 5 and 10)\n  10 < 5    returns false     (Boolean: is 10 less than 5?)\n  10 <? 5   returns 5         (Integer: still the lesser value)\n\nThe same pattern applies to the other coalescing operators:\n  >  returns Boolean          >?  returns the GREATER value\n  <= returns Boolean          <=? returns left if left <= right\n  >= returns Boolean          >=? returns left if left >= right\n\n<? is NOT less-than. It is coalescing minimum. It returns a value, not a Boolean.\n\nSee Q963 for <? in detail. See Q964 for >? in detail. See Q1050 for coalescing on classes.","ek9Example":"defines module qa.operators.lessthanvscoalescing\n\n  defines function\n\n    lesserOf() as pure\n      ->\n        left as Integer\n        right as Integer\n      <- rtn as Integer: left <? right\n\n    greaterOf() as pure\n      ->\n        left as Integer\n        right as Integer\n      <- rtn as Integer: left >? right\n\n  defines program\n\n    LessThanVsCoalescingDemo()\n      stdout <- Stdout()\n\n      scoreA <- 85\n      scoreB <- 92\n\n      //< returns Boolean\n      isLess <- scoreA < scoreB\n      stdout.println(`${scoreA} < ${scoreB} = ${isLess}`)\n\n      //<? returns the lesser VALUE\n      lesser <- lesserOf(scoreA, scoreB)\n      stdout.println(`${scoreA} <? ${scoreB} = ${lesser}`)\n\n      //>? returns the greater VALUE\n      greater <- greaterOf(scoreA, scoreB)\n      stdout.println(`${scoreA} >? ${scoreB} = ${greater}`)\n\n      //They are different types of result\n      stdout.println(`< gives Boolean: ${isLess}`)\n      stdout.println(`<? gives Integer: ${lesser}`)","migrationContext":"Java: Math.min(a,b) for minimum value, a < b for comparison. Python: min(a,b) vs a < b. EK9: a <? b for minimum value, a < b for comparison.","keywords":["boolean","coalescing","comparison","less","minimum","than","value"],"primaryTopics":["< vs <?","comparison vs coalescing"],"typicalErrors":[{"error":"E50001","correct":"      <- rtn as Integer: left <? right","incorrect":"      <- rtn as Integer: Math.min(left, right)","explanation":"EK9 has no Math class; use the <? coalescing operator to get the lesser of two values. See ek9 -h E50001 for details."}],"companions":[]}
{"id":1068,"category":"Override Mechanics","question":"I get E05120 saying override is required on my operator ?. How do I fix it?","url":"https://ek9.io/qa/QA1068.html","alternatePhrasings":["The compiler says override required on operator ? in my class","Fix E05120: override required on isSet operator","Why do I need override on operator ? when I defined it myself?"],"answer":"Every EK9 class inherits a default operator ? from its base type. When you define your own operator ?, you are OVERRIDING the inherited one. The compiler requires the 'override' keyword to make this explicit.\n\nBEFORE (E05120 error):\n  operator ? as pure\n    <- rtn as Boolean: name? and age?\n\nAFTER (fixed):\n  override operator ? as pure\n    <- rtn as Boolean: name? and age?\n\nThe same applies when using 'default operator' alongside a manual operator ?. If you write both, the manual one overrides the default:\n\n  default operator\n  override operator ? as pure\n    <- rtn as Boolean: name? and age?\n\nThe 'override' keyword is required on any operator that already exists in the parent type. For operator ?, this is always required because every type inherits it.\n\nOperators that typically need 'override':\n  override operator ?    isSet (always inherited)\n  override operator $    string (if parent defines it or default operator was used)\n  override operator ==   equality (if parent defines it)\n\nSee Q574 for override mechanics. See Q877 for the ? operator. See Q1055 for default vs manual operators.","ek9Example":"defines module qa.overridemechanics.fixe05120\n\n  defines class\n\n    Person\n      personName as String: String()\n      age as Integer: 0\n\n      default private Person()\n\n      Person()\n        ->\n          personName as String\n          age as Integer\n        this.personName :=: personName\n        this.age :=: age\n\n      //CORRECT: override is required because ? is inherited\n      override operator ? as pure\n        <- rtn as Boolean: personName? and age?\n\n      operator $ as pure\n        <- rtn as String: `${personName} (${age})`\n\n  defines program\n\n    OverrideDemo()\n      stdout <- Stdout()\n\n      person <- Person(\"Alice\", 30)\n      stdout.println(`Is set: ${person?}`)\n      stdout.println(`${person}`)","migrationContext":"Java: @Override annotation (optional but recommended). EK9: override keyword (mandatory). The compiler enforces it — no optional about it.","keywords":["E05120","class","fix","isSet","operator","override","required"],"primaryTopics":["E05120 fix","override operator ?"],"typicalErrors":[{"error":"E05120","correct":"      override operator ? as pure\n        <- rtn as Boolean: personName? and age?","incorrect":"      operator ? as pure\n        <- rtn as Boolean: name? and age?","explanation":"Add 'override' before 'operator ?'. Every class inherits operator ? from its base type, so your definition overrides it."}],"companions":[]}
{"id":1069,"category":"Override Mechanics","question":"Can I use default operator and also define my own operator ? on the same class?","url":"https://ek9.io/qa/QA1069.html","alternatePhrasings":["How do I mix default operator with a custom isSet check?","I get E02030 duplicate operator when I use default operator and operator ?","Should I use default operator or write my own operators?"],"answer":"Yes, but you must choose one of two approaches:\n\nAPPROACH 1 — default operator only (simplest):\n  defines class\n    Config\n      host as String: String()\n      port as Integer: 0\n      default operator\n\nThis generates ?, $, ==, <>, <=>, #? from all fields. Use this when field-by-field behaviour is correct.\n\nAPPROACH 2 — custom override then default operator last:\n  defines class\n    Config\n      host as String: String()\n      port as Integer: 0\n      override operator ? as pure\n        <- rtn as Boolean: host?\n      default operator\n\nIMPORTANT: 'default operator' must be the LAST item in the class body. Put your override operators BEFORE it. The 'override' keyword is required because default operator generates ? first.\n\nWHAT CAUSES E02030:\n  default operator\n  operator ? as pure           WRONG: duplicate, missing override\n    <- rtn as Boolean: host?\n\nThe fix is to add 'override':\n  default operator\n  override operator ? as pure  CORRECT: overrides the generated one\n    <- rtn as Boolean: host?\n\nAPPROACH 3 — no default, all manual:\n  operator ? as pure           No override needed — nothing to override\n    <- rtn as Boolean: host?\n  operator $ as pure\n    <- rtn as String: host\n\nWait — operator ? DOES need override even without default operator, because every class inherits ? from its base type. So always use 'override operator ?'.\n\n for E05120 fix. See Q116 for what default operator generates. See Q1055 for default vs manual.","ek9Example":"defines module qa.overridemechanics.defaultwithoverride\n\n  defines class\n\n    //APPROACH 1: default operator only\n    SimpleConfig\n      host as String: String()\n      port as Integer: 0\n\n      SimpleConfig()\n        ->\n          host as String\n          port as Integer\n        this.host :=: host\n        this.port :=: port\n\n      default operator\n\n    //APPROACH 2: custom override then default operator last\n    SmartConfig\n      host as String: String()\n      port as Integer: 0\n\n      SmartConfig()\n        ->\n          host as String\n          port as Integer\n        this.host :=: host\n        this.port :=: port\n\n      //Custom ? before default operator\n      override operator ? as pure\n        <- rtn as Boolean: host?\n\n      default operator\n\n  defines program\n\n    DefaultOverrideDemo()\n      stdout <- Stdout()\n\n      simple <- SimpleConfig(\"localhost\", 8080)\n      stdout.println(`Simple set: ${simple?}`)\n      stdout.println(`Simple: ${simple}`)\n\n      smart <- SmartConfig(\"localhost\", 8080)\n      stdout.println(`Smart set: ${smart?}`)\n      stdout.println(`Smart: ${smart}`)\n\n      //SmartConfig only checks host, not port\n      partialSmart <- SmartConfig(String(), 8080)\n      stdout.println(`Partial set: ${partialSmart?}`)","migrationContext":"Java: @Override on any method you override. EK9: override keyword mandatory on operators inherited from parent or generated by default operator.","keywords":["E02030","E05120","combine","default","duplicate","operator","override"],"primaryTopics":["default operator with override","E02030 duplicate operator"],"typicalErrors":[{"error":"E05120","correct":"default operator\n      override operator ? as pure\n        <- rtn as Boolean: host?","incorrect":"      default operator\n      operator ? as pure\n        <- rtn as Boolean: host?","explanation":"When using default operator alongside a manual operator ?, add 'override' to the manual one. Default operator generates ? first, your definition overrides it."}],"companions":[]}
{"id":1070,"category":"Override Mechanics","question":"Which operators always need the override keyword in EK9?","url":"https://ek9.io/qa/QA1070.html","alternatePhrasings":["When do I need to write override before an operator?","Does operator $ need override?","List all operators that require override in EK9"],"answer":"The operator ? ALWAYS needs override on any class. Every class inherits operator ? from its root type, so your definition always overrides it.\n\nALWAYS needs override:\n  override operator ? as pure    every class inherits this\n\nNeeds override when parent defines it OR default operator generates it:\n  override operator $ as pure    if parent has $ or default operator was used\n  override operator == as pure   if parent has == or default operator was used\n  override operator <> as pure   if parent has <>\n  override operator <=> as pure  if parent has <=>\n  override operator #? as pure   if parent has #?\n\nDoes NOT need override (no parent version exists):\n  operator $ as pure             if no parent $ and no default operator\n  operator #^ as pure            promote — rarely inherited\n  operator ++ / += / -=          mutation operators\n  operator :=: / :~: / :^:       copy/merge/replace\n\nSimple rule: if the compiler says E05120, add override. If it says E05110, remove override.\n\nE05120 = override required (parent has this operator)\nE05110 = override not valid (parent does NOT have this operator)\n\n for E05120 fix. See Q1069 for mixing default operator with overrides.","ek9Example":"defines module qa.overridemechanics.overridealways\n\n  defines class\n\n    //operator ? ALWAYS needs override\n    Account\n      accountId as Integer: 0\n\n      default private Account()\n\n      Account()\n        -> accountId as Integer\n        this.accountId :=: accountId\n\n      override operator ? as pure\n        <- rtn as Boolean: accountId?\n\n      //operator $ does NOT need override here (no parent $)\n      operator $ as pure\n        <- rtn as String: `Account-${accountId}`\n\n  defines program\n\n    OverrideAlwaysDemo()\n      stdout <- Stdout()\n\n      acct <- Account(1001)\n      stdout.println(`Set: ${acct?}`)\n      stdout.println(`${acct}`)","migrationContext":"Java: @Override is optional but recommended. EK9: override is mandatory — the compiler enforces it.","keywords":["E05110","E05120","always","isSet","list","operator","override"],"primaryTopics":["which operators need override","override rules"],"typicalErrors":[{"error":"E05120","correct":"      override operator ? as pure\n        <- rtn as Boolean: accountId?","incorrect":"      operator ? as pure\n        <- rtn as Boolean: name?","explanation":"operator ? always needs 'override' because every class inherits it from its base type."}],"companions":[]}
{"id":1071,"category":"Generics","question":"Why does the compiler require two constructors for my generic class?","url":"https://ek9.io/qa/QA1071.html","alternatePhrasings":["What is E06040 and how do I fix it?","My generic class gets 'requires 2 constructors' error","How do I define constructors for a generic type in EK9?"],"answer":"EK9 generic types require exactly two constructors: a default (no-arg) constructor and a parameterised constructor.\n\nWhy two constructors?\n1. The DEFAULT constructor creates an unset instance (needed for type inference)\n2. The PARAMETERISED constructor creates a set instance with values\n\nBEFORE (E06040 error — only one constructor):\n  defines class\n    Pair of type (A, B)\n      first as A: A()\n      second as B: B()\n\n      Pair()\n        -> first as A, second as B\n        this.first :=: first\n        this.second :=: second\n\nAFTER (fixed — both constructors):\n  defines class\n    Pair of type (A, B)\n      first as A: A()\n      second as B: B()\n\n      default Pair()\n\n      Pair()\n        -> first as A, second as B\n        this.first :=: first\n        this.second :=: second\n\nThe default constructor must be public for generic types (E06060 prevents private). The compiler uses it internally for type inference.\n\nSee Q194 for generic class basics. See Q196 for multi-parameter generics. See Q116 for default operator.","ek9Example":"defines module qa.generics.twoconstructors\n\n  defines class\n\n    Pair of type (A, B)\n      first as A: A()\n      second as B: B()\n\n      //Constructor 1: default (used by compiler for type inference)\n      default Pair()\n\n      //Constructor 2: parameterised (public — used by code)\n      Pair()\n        ->\n          first as A\n          second as B\n        this.first :=: first\n        this.second :=: second\n\n      override operator ? as pure\n        <- rtn as Boolean: first? and second?\n\n      operator $ as pure\n        <- rtn as String: `(${$first}, ${$second})`\n\n  defines program\n\n    GenericConstructorDemo()\n      stdout <- Stdout()\n\n      pair <- Pair(\"Alice\", 30)\n      stdout.println(`Pair: ${pair}`)","migrationContext":"Java: generics have no constructor requirements. Rust: no constructors, uses associated functions. EK9: generic types MUST have exactly 2 constructors — default (private) and parameterised.","keywords":["E06040","constructor","default","generic","private","two","type"],"primaryTopics":["E06040 fix","generic constructor requirement"],"typicalErrors":[{"error":"E06040","correct":"    Pair of type (A, B)\n      first as A: A()\n      second as B: B()\n\n      //Constructor 1: default (used by compiler for type inference)\n      default Pair()\n\n      //Constructor 2: parameterised (public — used by code)","incorrect":"    Pair of type (A, B)\n      first as A: A()\n      second as B: B()\n\n      Pair()\n        -> first as A, second as B","explanation":"Generic types require 2 constructors: a default (no-arg) and a parameterised one. Add 'default private Pair()' for the default constructor."}],"companions":[]}
{"id":1072,"category":"Functions and Methods","question":"Why does my abstract function get E08180 on the return value?","url":"https://ek9.io/qa/QA1072.html","alternatePhrasings":["How do I declare the return value on an abstract function?","What is E08180 variable not initialised in an abstract function?","Should I use String? or String() for abstract function returns?"],"answer":"Abstract function return values must be declared with ? (unset marker) or initialised with a default value.\n\nBEFORE (E08180 error):\n  transformer() as abstract\n    -> input as String\n    <- rtn as String\n\nThe problem: 'rtn as String' has no initial value and no ? marker. The compiler cannot guarantee it will be set.\n\nAFTER — Option 1 (? marker — returns Optional-like):\n  transformer() as abstract\n    -> input as String\n    <- rtn as String?\n\nAFTER — Option 2 (default value):\n  transformer() as abstract\n    -> input as String\n    <- rtn as String: String()\n\nBoth work. Use ? when the function might legitimately return unset. Use a default value when a fallback makes sense.\n\nFor dynamic functions implementing the abstract, the return variable inherits the declaration:\n  myFn <- (prefix) is transformer as function\n    rtn: prefix + input\n\nSee Q51 for abstract function types. See Q52 for dynamic functions.  for override on operators.","ek9Example":"defines module qa.functions.abstractreturn\n\n  defines function\n\n    //Abstract with ? marker on return\n    transformer() as abstract\n      -> input as String\n      <- rtn as String?\n\n  defines program\n\n    AbstractReturnDemo()\n      stdout <- Stdout()\n\n      //Dynamic function implementing the abstract\n      prefix <- \"Hello: \"\n      myFn <- (prefix) is transformer as function\n        rtn: prefix + input\n\n      result <- myFn(\"World\")\n      stdout.println(result)","migrationContext":"Java: abstract methods declare return type only. Python: no return type. EK9: abstract function return variables must be ? or initialised.","keywords":["E08180","abstract","function","initialise","return","unset"],"primaryTopics":["E08180 fix","abstract function return"],"typicalErrors":[{"error":"E08180","correct":"    transformer() as abstract\n      -> input as String\n      <- rtn as String?","incorrect":"    transformer() as abstract\n      -> input as String\n      <- rtn as String","explanation":"Abstract function return values need ? (unset marker) or a default initialiser. Use '<- rtn as String?' to allow unset returns."}],"companions":[]}
{"id":1073,"category":"Streams and Pipelines","question":"How do I sort a stream in descending order in EK9?","url":"https://ek9.io/qa/QA1073.html","alternatePhrasings":["Can I reverse sort a stream pipeline?","How do I get the top N largest items from a list?","How does sort by work with a comparator function?"],"answer":"EK9 has two sort forms in stream pipelines:\n\n1. 'sort' — uses the natural ordering (ascending via <=> operator)\n2. 'sort by comparatorFunction' — uses a custom comparator function\n\nASCENDING (natural order):\n  cat scores | sort | head 3 | collect as List of Integer\n\nDESCENDING (custom comparator):\n  cat scores | sort by descending | head 3 | collect as List of Integer\n\nThe comparator function must take two parameters of the same type and return Integer:\n  descending() as pure\n    -> left as Integer, right as Integer\n    <- rtn as Integer: right <=> left\n\nNote: right <=> left reverses the order (compare right to left instead of left to right).\n\nFor ascending, left <=> right. For descending, right <=> left.\n\nYou can also use 'tail N' with ascending sort to get the N largest:\n  cat scores | sort | tail 3 | collect as List of Integer\n\nSee Q979 for sort by examples. See Q120 for collection sorting. See Q235 for complete stream operations.","ek9Example":"defines module qa.streams.sortdescending\n\n  defines function\n\n    //Descending comparator: reverse the <=> order\n    descending() as pure\n      ->\n        left as Integer\n        right as Integer\n      <- rtn as Integer: right <=> left\n\n  defines program\n\n    SortDemo()\n      stdout <- Stdout()\n\n      scores <- [85, 42, 91, 67, 73, 55, 88, 96, 61, 79]\n      stdout.println(`All: ${scores}`)\n\n      //Top 3 largest: sort descending with comparator, take head\n      topThree <- cat scores\n        | sort by descending\n        | head 3\n        | collect as List of Integer\n      stdout.println(`Top 3: ${topThree}`)\n\n      //Bottom 3 smallest: sort ascending (natural), take head\n      bottomThree <- cat scores\n        | sort\n        | head 3\n        | collect as List of Integer\n      stdout.println(`Bottom 3: ${bottomThree}`)","migrationContext":"Java: stream().sorted(Comparator.reverseOrder()). Python: sorted(items, reverse=True). Rust: sort().rev(). EK9: sort by with reversed comparator (right <=> left).","keywords":["comparator","descending","head","pipeline","reverse","sort","stream"],"primaryTopics":["descending sort","sort by comparator","stream sort"],"typicalErrors":[],"companions":[]}
{"id":1074,"category":"Advanced Type System","question":"How do I use a constrained type as a field in a record?","url":"https://ek9.io/qa/QA1074.html","alternatePhrasings":["Can I put a constrained type in a record constructor?","Why does my constrained type field get a method resolution error?","How do I create a record with a Percentage field?"],"answer":"Constrained types work as record fields, but the constructor parameter must match the constrained type, not the base type.\n\nDefine the constrained type:\n  defines type\n    Percentage as Integer constrain as\n      >= 0 and <= 100\n\nUse it in a record:\n  defines record\n    ExamResult\n      studentName as String: String()\n      score as Percentage: Percentage(0)\n\n      ExamResult()\n        -> studentName as String, score as Percentage\n        this.studentName :=: studentName\n        this.score :=: score\n\n      default operator\n\nIMPORTANT: The constructor parameter must be 'score as Percentage', not 'score as Integer'. Even though Percentage is based on Integer, they are distinct types.\n\nIn the program, create with the constrained type:\n  result <- ExamResult(\"Alice\", Percentage(85))\n\nNot:\n  result <- ExamResult(\"Alice\", 85)    WRONG — 85 is Integer, not Percentage\n\nSee Q257 for constrained type basics. See Q717 for constrained type comparisons. See Q720 for constrainable types.","ek9Example":"defines module qa.advancedtypes.constrainedinrecord\n\n  defines type\n\n    Percentage as Integer constrain as\n      >= 0 and <= 100\n\n  defines record\n\n    ExamResult\n      studentName as String: String()\n      score as Percentage: Percentage(0)\n\n      ExamResult()\n        ->\n          studentName as String\n          score as Percentage\n        this.studentName :=: studentName\n        this.score :=: score\n\n      default operator\n\n  defines program\n\n    ConstrainedRecordDemo()\n      stdout <- Stdout()\n\n      result <- ExamResult(\"Alice\", Percentage(85))\n      stdout.println(`${result}`)","migrationContext":"Java: no direct equivalent. Rust: newtype pattern. Python: no constraint at type level. EK9: constrained types are distinct types — constructor must use the constrained type.","keywords":["constrained","field","integer","percentage","record","type"],"primaryTopics":["constrained type in record","constructor type matching"],"typicalErrors":[{"error":"E50060","correct":"      result <- ExamResult(\"Alice\", Percentage(85))","incorrect":"      result <- ExamResult(\"Alice\", 85)","explanation":"85 is an Integer, not a Percentage. Use Percentage(85) to create the constrained type value explicitly."}],"companions":[]}
{"id":1075,"category":"Operators and Expressions","question":"Copy all fields from one record to another.","url":"https://ek9.io/qa/QA1075.html","alternatePhrasings":["Transfer field values between two records using :=:","Clone a record into a second record in EK9","In Java I'd use clone() or a copy constructor. What's the EK9 equivalent?","I have two Config records and need to make the second match the first"],"answer":"Copy with :=: — target gets all field values from source:\n\n  backup <- ServerConfig()\n  backup :=: original\n  stdout.println(`Backup: ${backup}`)\n\nSource is unchanged. Use 'default operator' to auto-generate :=: from fields.\n\nSee Q1058 for copy vs merge vs replace. See Q1076 for partial copies.","ek9Example":"defines module qa.operators.copyrecorddata\n\n  defines class\n\n    ServerConfig\n      host <- String()\n      port <- Integer()\n      maxConn <- Integer()\n\n      ServerConfig()\n        ->\n          host as String\n          port as Integer\n          maxConn as Integer\n        this.host :=: host\n        this.port :=: port\n        this.maxConn :=: maxConn\n\n      default operator ?\n      default operator $\n      default operator :=:\n\n  defines program\n\n    CopyRecordDataDemo()\n      stdout <- Stdout()\n\n      //Create a fully configured server\n      primary <- ServerConfig(\"db.prod.internal\", 5432, 100)\n      stdout.println(`Primary: ${primary}`)\n\n      //Copy all fields to a backup config\n      backup <- ServerConfig()\n      backup :=: primary\n      stdout.println(`Backup: ${backup}`)\n\n      //Copy overwrites existing values too\n      staging <- ServerConfig(\"staging.local\", 3000, 10)\n      stdout.println(`Staging before: ${staging}`)\n      staging :=: primary\n      stdout.println(`Staging after: ${staging}`)","migrationContext":"Java: clone() or copy constructor. Python: copy.copy(). Rust: Clone trait. Go: struct assignment. EK9: :=: operator copies all fields from source to target.","keywords":[":=:","clone","copy","duplicate","fields","record","transfer"],"primaryTopics":[":=: copy operator","record field copy"],"typicalErrors":[{"error":"E50060","correct":"backup :=: primary","incorrect":"backup :=: primary.clone()","explanation":"EK9 records have no clone() method, so primary.clone() is unresolved — use the :=: copy operator to copy all fields. See ek9 -h E50060 for details."}],"companions":[]}
{"id":1076,"category":"Operators and Expressions","question":"Copy a record that has some fields unset to another record.","url":"https://ek9.io/qa/QA1076.html","alternatePhrasings":["I have a user profile with only name and email filled in and need to duplicate it","In Java clone() copies all fields including nulls. Does EK9 :=: copy unset fields too?","Given a partially-initialised config, create an exact copy preserving the unset state","Transfer partial data between two objects using :=:"],"answer":":=: copies ALL fields including unset ones:\n\n  partial <- UserProfile(\"Alice\", \"alice@example.com\")\n  copy <- UserProfile()\n  copy :=: partial\n  stdout.println(`Copy set?: ${copy?}`)\n\nPhone stays unset in the copy — :=: is a faithful snapshot. To copy only SET fields, use :~: merge instead.\n\nSee Q1075 for full copy. See Q1077 for merge. See Q1058 for overview.","ek9Example":"defines module qa.operators.copypartialrecord\n\n  defines class\n\n    UserProfile\n      name <- String()\n      email <- String()\n      phone <- String()\n\n      UserProfile()\n        ->\n          name as String\n          email as String\n        this.name :=: name\n        this.email :=: email\n        //phone remains unset\n\n      default operator ?\n      default operator $\n      default operator :=:\n\n  defines program\n\n    CopyPartialRecordDemo()\n      stdout <- Stdout()\n\n      //Only name and email are set, phone is unset\n      partial <- UserProfile(\"Alice\", \"alice@example.com\")\n      stdout.println(`Source: ${partial}`)\n      stdout.println(`Source set?: ${partial?}`)\n\n      //Copy transfers everything — including unset phone\n      copy <- UserProfile()\n      copy :=: partial\n      stdout.println(`Copy: ${copy}`)\n      stdout.println(`Copy set?: ${copy?}`)","migrationContext":"Java: no equivalent — clone() copies all fields. Python: copy.copy() copies all. EK9 distinguishes :=: (copy all including unset) from :~: (merge only set fields).","keywords":[":=:","copy","fields","incomplete","partial","tri-state","unset"],"primaryTopics":[":=: with partial data","unset field copying"],"typicalErrors":[],"companions":[]}
{"id":1077,"category":"Operators and Expressions","question":"Merge only the set fields from one record into another.","url":"https://ek9.io/qa/QA1077.html","alternatePhrasings":["Apply a partial update to an existing record using :~:","Patch a record with only the fields that have values","In Java I'd check each field for null before copying. What's the EK9 way?","I have an update object with some fields filled in and want to apply it to my main record"],"answer":"Implement :~: to guard each field, then merge:\n\n  operator :~:\n    -> from as Address\n    if from.street?\n      street :=: from.street\n    if from.postcode?\n      postcode :=: from.postcode\n\n  addr :~: update\n\nOnly SET fields from the source are applied. Unset fields leave the target unchanged.\n\nSee Q1075 for :=: copy. See Q1058 for overview.","ek9Example":"defines module qa.operators.mergepartialrecord\n\n  defines class\n\n    Address\n      street <- String()\n      city <- String()\n      postcode <- String()\n\n      Address()\n        ->\n          street as String\n          city as String\n          postcode as String\n        this.street :=: street\n        this.city :=: city\n        this.postcode :=: postcode\n\n      //Constructor for partial updates — only postcode set\n      Address()\n        -> postcode as String\n        this.postcode :=: postcode\n\n      //Merge only copies SET fields from source\n      operator :~:\n        -> from as Address\n        if from.street?\n          street :=: from.street\n        if from.city?\n          city :=: from.city\n        if from.postcode?\n          postcode :=: from.postcode\n\n      default operator ?\n      default operator $\n      default operator :=:\n\n  defines program\n\n    MergePartialRecordDemo()\n      stdout <- Stdout()\n\n      //Existing address with all fields set\n      addr <- Address(\"10 High Street\", \"London\", \"SW1A 1AA\")\n      stdout.println(`Before: ${addr}`)\n\n      //Partial update — only postcode is set via single-arg constructor\n      update <- Address(\"EC2R 8AH\")\n      stdout.println(`Update: ${update}`)\n\n      //Merge applies only the set postcode, leaves street and city alone\n      addr :~: update\n      stdout.println(`After merge: ${addr}`)","migrationContext":"Java: manual null checks per field. Python: dict.update() with filtering. Go: manual field-by-field with zero-value checks. EK9: :~: merge operator with ? guards per field.","keywords":[":~:","guard","merge","partial","patch","set fields only","update"],"primaryTopics":[":~: merge operator","partial update pattern"],"typicalErrors":[{"error":"E50060","correct":"      addr :~: update","incorrect":"      addr.merge(update)","explanation":"EK9 has no '.merge()' method; use the ':~:' operator to merge only the set fields of a record. See ek9 -h E50060 for details."}],"companions":[]}
{"id":1078,"category":"Operators and Expressions","question":"Replace all content in a List with another List.","url":"https://ek9.io/qa/QA1078.html","alternatePhrasings":["Overwrite a list entirely using :^:","Swap the contents of one collection with another","In Java I'd clear() then addAll(). How do I replace list contents in EK9?","I need to replace all items in a list with items from a different list"],"answer":"Replace with :^: — target gets all source items:\n  tasks :^: newSprint\n\nThe target list is cleared and filled with the source's items. The source is unchanged.\n\nSee Q1075 for :=: copy. See Q1077 for :~: merge.","ek9Example":"defines module qa.operators.replacerecordcontents\n\n  defines class\n\n    Task\n      name <- String()\n      priority <- Integer()\n\n      Task()\n        ->\n          name as String\n          priority as Integer\n        this.name :=: name\n        this.priority :=: priority\n\n      default operator\n\n  defines program\n\n    ReplaceRecordContentsDemo()\n      stdout <- Stdout()\n\n      //Current task list\n      tasks <- List() of Task\n      tasks += Task(\"Fix login bug\", 1)\n      tasks += Task(\"Update docs\", 3)\n      stdout.println(`Current tasks: ${tasks}`)\n\n      //New sprint tasks to replace the current list entirely\n      newSprint <- List() of Task\n      newSprint += Task(\"Deploy v2.0\", 1)\n      newSprint += Task(\"Performance audit\", 2)\n      newSprint += Task(\"Add monitoring\", 3)\n\n      //Replace all current tasks with the new sprint\n      tasks :^: newSprint\n      stdout.println(`After replace: ${tasks}`)","migrationContext":"Java: list.clear(); list.addAll(other). Python: target[:] = source. Go: target = make([]T, len(source)); copy(target, source). EK9: target :^: source replaces contents in place.","keywords":[":^:","collection","list","overwrite","replace","swap contents"],"primaryTopics":[":^: replace on collections","list content replacement"],"typicalErrors":[{"error":"E50060","correct":"tasks :^: newSprint","incorrect":"tasks.addAll(newSprint)","explanation":"EK9 List has no 'addAll' method — use the ':^:' replace operator to overwrite a collection's contents in place. See ek9 -h E50060 for details."}],"companions":[]}
{"id":1079,"category":"Operators and Expressions","question":"Duplicate an endpoint config, then apply a partial timeout update to it.","url":"https://ek9.io/qa/QA1079.html","alternatePhrasings":["I need to back up a config with :=: then patch one field using :~:","In Python I'd copy then dict.update(). Write the EK9 equivalent with :=: and :~:","Given an Endpoint, create an exact copy then merge in a timeout-only change","Copy all fields from one record, then merge only the set fields from a patch"],"answer":"Copy all fields, then merge a partial update:\n  copied :=: original\n  target :~: patch\n\n:=: copies ALL fields (set and unset). :~: copies only SET fields, leaving unset fields in the target unchanged.\n\nSee Q1075 for copy details. See Q1077 for merge details.","ek9Example":"defines module qa.operators.copyvsmergereplace\n\n  defines class\n\n    Endpoint\n      url <- String()\n      timeout <- Integer()\n      retries <- Integer()\n\n      Endpoint()\n        ->\n          url as String\n          timeout as Integer\n          retries as Integer\n        this.url :=: url\n        this.timeout :=: timeout\n        this.retries :=: retries\n\n      //Constructor for partial updates — only timeout\n      Endpoint()\n        -> timeout as Integer\n        this.timeout :=: timeout\n\n      operator :~:\n        -> from as Endpoint\n        if from.url?\n          url :=: from.url\n        if from.timeout?\n          timeout :=: from.timeout\n        if from.retries?\n          retries :=: from.retries\n\n      default operator ?\n      default operator $\n      default operator :=:\n\n  defines program\n\n    CopyVsMergeReplaceDemo()\n      stdout <- Stdout()\n\n      original <- Endpoint(\"https://api.example.com\", 30, 3)\n\n      // :=: COPY — exact duplicate of all fields\n      copied <- Endpoint()\n      copied :=: original\n      stdout.println(`Copy: ${copied}`)\n\n      // :~: MERGE — only the set timeout field is applied\n      patch <- Endpoint(60)\n      target <- Endpoint(\"https://old.example.com\", 10, 1)\n      stdout.println(`Before merge: ${target}`)\n      target :~: patch\n      stdout.println(`After merge: ${target}`)","migrationContext":"Java: no distinction — clone() copies all. Python: copy vs dict.update(). Go: struct assignment vs manual field merge. EK9: :=: (duplicate all) vs :~: (patch set fields only).","keywords":[":=:",":~:","comparison","copy","difference","merge","partial update"],"primaryTopics":["copy vs merge",":=: vs :~: comparison"],"typicalErrors":[],"companions":[]}
{"id":1080,"category":"Operators and Expressions","question":"Copy a child class including parent fields.","url":"https://ek9.io/qa/QA1080.html","alternatePhrasings":["I have a Car that extends Vehicle and need to copy all fields including make and year","In Java I'd call super.clone() in a subclass. Write the EK9 equivalent","Given a class hierarchy with Vehicle and Car, duplicate a Car preserving parent fields","Transfer all data from one extended object to another using :=:"],"answer":"Copy an inherited type with :=: — parent and child fields included:\n  dst :=: src\n\nBoth parent and child must declare 'default operator :=:'. The parent must be declared 'as open' to allow extension.\n\nSee Q1075 for basic copy.","ek9Example":"defines module qa.operators.copywithinheritance\n\n  defines class\n\n    Vehicle as open\n      make <- String()\n      year <- Integer()\n\n      Vehicle()\n        ->\n          make as String\n          year as Integer\n        this.make :=: make\n        this.year :=: year\n\n      default operator ?\n      default operator $\n      default operator :=:\n\n    Car extends Vehicle\n      doors <- Integer()\n\n      Car()\n        ->\n          make as String\n          year as Integer\n          doors as Integer\n        super(make, year)\n        this.doors :=: doors\n\n      default operator ?\n      default operator $\n      default operator :=:\n\n  defines program\n\n    CopyWithInheritanceDemo()\n      stdout <- Stdout()\n\n      //Source has parent fields (make, year) and child field (doors)\n      src <- Car(\"Toyota\", 2024, 4)\n      stdout.println(`Source: ${src}`)\n\n      //Copy transfers all fields including inherited ones\n      dst <- Car()\n      dst :=: src\n      stdout.println(`Copy: ${dst}`)\n      stdout.println(`Copy set?: ${dst?}`)","migrationContext":"Java: clone() with super.clone(). Python: copy.deepcopy() handles inheritance. Go: embed + struct copy. EK9: default operator :=: on both parent and child handles the chain automatically.","keywords":[":=:","child","copy","extends","inheritance","parent","super"],"primaryTopics":[":=: with inheritance","copying inherited fields"],"typicalErrors":[{"error":"E05030","correct":"    Vehicle as open","incorrect":"    Vehicle","explanation":"The parent class must be declared 'as open' to allow extension — EK9 types are closed by default. See ek9 -h E05030 for details."}],"companions":[]}
{"id":1081,"category":"Operators and Expressions","question":"Merge two lists together using :~: operator.","url":"https://ek9.io/qa/QA1081.html","alternatePhrasings":["Add all elements from one list into another list","Combine two lists in place using :~:","In Java I'd use addAll(). What is the EK9 equivalent?","I have two lists and need to merge the second into the first"],"answer":"Merge one list into another with :~::\n  team :~: newHires\n\nAll elements from the source are added to the end of the target. The source is unchanged.\n\nSee Q1058 for copy vs merge vs replace.","ek9Example":"defines module qa.operators.mergecollection\n\n  defines program\n\n    MergeCollectionDemo()\n      stdout <- Stdout()\n\n      //Two separate lists of names\n      team <- [\"Alice\", \"Bob\", \"Charlie\"]\n      newHires <- [\"Diana\", \"Eve\"]\n      stdout.println(`Team: ${team}`)\n      stdout.println(`New hires: ${newHires}`)\n\n      //Merge new hires into the team list\n      team :~: newHires\n      stdout.println(`After merge: ${team}`)\n\n      //Merge a single element\n      team :~: \"Frank\"\n      stdout.println(`After single merge: ${team}`)","migrationContext":"Java: list.addAll(other). Python: list.extend(other). Go: append(slice, other...). EK9: list :~: otherList or list += otherList.","keywords":[":~:","addAll","append","collection","combine","list","merge"],"primaryTopics":[":~: on collections","list merging"],"typicalErrors":[{"error":"E50060","correct":"team :~: newHires","incorrect":"team.addAll(newHires)","explanation":"EK9's List has no addAll method — use the merge operator ':~:' to add all elements of one list into another. See ek9 -h E50060 for details."}],"companions":[]}
{"id":1082,"category":"Operators and Expressions","question":"Compare two Product records by price.","url":"https://ek9.io/qa/QA1082.html","alternatePhrasings":["Write code that checks which of two records is greater","Determine ordering between two objects using <=>","In Java I'd use Comparable.compareTo(). What is the EK9 equivalent?","I have two products and need to know which one costs more"],"answer":"Compare two records with <=> and use the derived operators:\n  cmp <- itemA <=> itemB\n  if itemA > itemB\n    stdout.println(\"A is greater\")\n\n'default operator' generates <=> and all derived comparison operators (==, <>, <, >, <=, >=).\n\nSee Q1085 for sorting records. See Q1083 for equality checking.","ek9Example":"defines module qa.operators.comparetworecords\n\n  defines class\n\n    Product\n      name <- String()\n      price <- Float()\n\n      Product()\n        ->\n          name as String\n          price as Float\n        this.name :=: name\n        this.price :=: price\n\n      default operator\n\n  defines program\n\n    CompareRecordsDemo()\n      stdout <- Stdout()\n\n      itemA <- Product(\"Keyboard\", 49.99)\n      itemB <- Product(\"Mouse\", 29.99)\n\n      cmp <- itemA <=> itemB\n      stdout.println(`Compare: ${cmp}`)\n\n      if itemA > itemB\n        stdout.println(`${itemA} costs more`)\n      else\n        stdout.println(`${itemB} costs more`)\n\n      if itemA == itemB\n        stdout.println(\"Same product\")\n      else\n        stdout.println(\"Different products\")","migrationContext":"Java: implements Comparable<T>, compareTo(). Python: __lt__, __eq__ or functools.total_ordering. Rust: impl Ord. Go: manual comparison. EK9: default operator <=> or custom operator <=>.","keywords":["<=>","compare","comparison","greater","less","ordering","record"],"primaryTopics":["<=> comparison operator","record ordering"],"typicalErrors":[{"error":"E50060","correct":"cmp <- itemA <=> itemB","incorrect":"cmp <- itemA.compareTo(itemB)","explanation":"EK9 has no .compareTo() method; use the '<=>' operator for three-way comparison. See ek9 -h E50060 for details."}],"companions":[]}
{"id":1083,"category":"Operators and Expressions","question":"Check if two Config records have identical values.","url":"https://ek9.io/qa/QA1083.html","alternatePhrasings":["Test equality between two objects using ==","Determine if two records contain the same data","In Java I'd use .equals(). How do I check equality in EK9?","I have two config objects and need to verify they match"],"answer":"Check equality and inequality with == and <>:\n  if primary == replica\n    stdout.println(\"match\")\n  if primary <> staging\n    stdout.println(\"differ\")\n\n'default operator' generates == and <> from the <=> comparison. Both objects must be SET for meaningful comparison.\n\nSee Q1082 for ordering comparison.","ek9Example":"defines module qa.operators.recordequality\n\n  defines class\n\n    Config\n      host <- String()\n      port <- Integer()\n\n      Config()\n        ->\n          host as String\n          port as Integer\n        this.host :=: host\n        this.port :=: port\n\n      default operator\n\n  defines program\n\n    RecordEqualityDemo()\n      stdout <- Stdout()\n\n      primary <- Config(\"db.prod\", 5432)\n      replica <- Config(\"db.prod\", 5432)\n      staging <- Config(\"db.stage\", 5432)\n\n      //Equal values\n      if primary == replica\n        stdout.println(\"Primary and replica match\")\n\n      //Different values\n      if primary <> staging\n        stdout.println(\"Primary and staging differ\")","migrationContext":"Java: .equals() and Objects.equals(). Python: == (with __eq__). Rust: PartialEq. Go: reflect.DeepEqual or manual. EK9: default operator == or custom operator ==.","keywords":["<>","==","compare","equal","equality","identical","match"],"primaryTopics":["== equality operator","record equality"],"typicalErrors":[{"error":"E50060","correct":"if primary == replica","incorrect":"if primary.equals(replica)","explanation":"EK9 has no .equals() method; use the == operator for equality and <> for inequality. See ek9 -h E50060 for details."}],"companions":[]}
{"id":1084,"category":"Operators and Expressions","question":"Guard a comparison result when one sensor reading might be unset.","url":"https://ek9.io/qa/QA1084.html","alternatePhrasings":["I have two sensor objects and one might be uninitialised — compare them safely","In Java this would throw NullPointerException. Write the safe EK9 comparison","Given an active and an offline sensor, compare them and handle the unset case","Check whether a <=> comparison result is valid before using it"],"answer":"Guard a comparison result when an operand might be unset:\n  cmp <- active <=> offline\n  if cmp?\n    stdout.println($cmp)\n\nWhen either operand is unset, <=> produces an unset result. Always guard with ? before using the value.\n\nSee Q1082 for normal comparison. See Q1087 for checking if records are set.","ek9Example":"defines module qa.operators.compareunsetrecords\n\n  defines class\n\n    Sensor\n      name <- String()\n      reading <- Float()\n\n      Sensor()\n        ->\n          name as String\n          reading as Float\n        this.name :=: name\n        this.reading :=: reading\n\n      default operator\n\n  defines program\n\n    CompareUnsetRecordsDemo()\n      stdout <- Stdout()\n\n      active <- Sensor(\"Temperature\", 23.5)\n      offline <- Sensor()\n      stdout.println(`Active: ${active}`)\n      stdout.println(`Offline set?: ${offline?}`)\n\n      //Compare set with unset — result is unset\n      cmp <- active <=> offline\n      if cmp?\n        stdout.println(`Comparison: ${cmp}`)\n      else\n        stdout.println(\"Comparison unset: cannot compare with unset sensor\")","migrationContext":"Java: NullPointerException if null. Python: TypeError on None comparison. Go: panic on nil. EK9: comparison with unset object returns unset result — no crash, no wrong answer.","keywords":["<=>","absent","check result","compare","guard","tri-state","unset"],"primaryTopics":["comparison with unset objects","tri-state comparison"],"typicalErrors":[{"error":"E07540","correct":"      if cmp?","incorrect":"      if cmp","explanation":"A <=> comparison can be unset, so branch on the guarded result `if cmp?` (a Boolean); branching on the raw Integer `if cmp` is not Boolean-compatible and triggers E07540. See ek9 -h E07540 for details."}],"companions":[]}
{"id":1085,"category":"Operators and Expressions","question":"Sort a list of Employee records by salary.","url":"https://ek9.io/qa/QA1085.html","alternatePhrasings":["Order objects in a list using a stream sort pipeline","Arrange records from lowest to highest value","In Java I'd use Collections.sort with Comparator.comparing(). How in EK9?","I have a list of employees and need to sort them by pay"],"answer":"Sort records via stream pipeline using default <=>:\n  sorted <- cat staff | sort | collect as List of Employee\n\n'default operator' generates <=> across all fields in declaration order. For field-specific sorting, implement a custom <=>.\n\nSee Q1073 for descending sort. See Q1082 for basic comparison.","ek9Example":"defines module qa.operators.sortrecordsbyfield\n\n  defines class\n\n    Employee\n      name <- String()\n      salary <- Float()\n\n      Employee()\n        ->\n          name as String\n          salary as Float\n        this.name :=: name\n        this.salary :=: salary\n\n      default operator\n\n  defines program\n\n    SortRecordsByFieldDemo()\n      stdout <- Stdout()\n\n      staff <- List() of Employee\n      staff += Employee(\"Alice\", 75000.0)\n      staff += Employee(\"Bob\", 55000.0)\n      staff += Employee(\"Charlie\", 92000.0)\n      staff += Employee(\"Diana\", 68000.0)\n\n      stdout.println(\"Unsorted:\")\n      cat staff > stdout\n\n      //Sort by natural ordering (fields in declaration order: name then salary)\n      sorted <- cat staff | sort | collect as List of Employee\n      stdout.println(\"Sorted:\")\n      cat sorted > stdout","migrationContext":"Java: Collections.sort(list, Comparator.comparing(Employee::getSalary)). Python: sorted(employees, key=lambda e: e.salary). Rust: sort_by_key(). EK9: cat list | sort | collect as List of T.","keywords":["ascending","list","order","pipeline","record","sort","stream"],"primaryTopics":["sorting records","stream sort pipeline"],"typicalErrors":[{"error":"E50060","correct":"sorted <- cat staff | sort | collect as List of Employee","incorrect":"sorted <- staff.sort()","explanation":"EK9 List has no .sort() method; sort via a stream pipeline 'cat list | sort | collect as List of T'. See ek9 -h E50060 for details."}],"companions":[]}
{"id":1086,"category":"Operators and Expressions","question":"Find the cheapest product in a list.","url":"https://ek9.io/qa/QA1086.html","alternatePhrasings":["Get the minimum item from a collection using a stream","Extract the smallest record from a sorted list","In Python I'd use min(). How do I find the minimum in EK9?","I have a list of products and need the one with the lowest price"],"answer":"Find the minimum with sort ascending + head 1:\n  cheapest <- cat products | sort | head 1 | collect as List of Product\n\nSort + head 1 is the standard min pattern. For the maximum, use a descending comparator with 'sort by'.\n\nSee Q1085 for sorting records. See Q1073 for descending sort.","ek9Example":"defines module qa.operators.findcheapestproduct\n\n  defines class\n\n    Product\n      name <- String()\n      price <- Float()\n\n      Product()\n        ->\n          name as String\n          price as Float\n        this.name :=: name\n        this.price :=: price\n\n      default operator\n\n  defines function\n\n    descending() as pure\n      ->\n        left as Product\n        right as Product\n      <- rtn as Integer: right <=> left\n\n  defines program\n\n    FindCheapestProductDemo()\n      stdout <- Stdout()\n\n      products <- List() of Product\n      products += Product(\"Keyboard\", 49.99)\n      products += Product(\"Mouse\", 19.99)\n      products += Product(\"Monitor\", 299.99)\n      products += Product(\"Cable\", 9.99)\n\n      //Find cheapest: sort ascending, take first\n      cheapest <- cat products | sort | head 1 | collect as List of Product\n      stdout.println(`Cheapest: ${cheapest}`)\n\n      //Find most expensive: sort descending, take first\n      priciest <- cat products | sort by descending | head 1 | collect as List of Product\n      stdout.println(`Priciest: ${priciest}`)","migrationContext":"Java: Collections.min(list, comparator). Python: min(products, key=lambda p: p.price). Rust: iter().min_by_key(). EK9: cat list | sort | head 1 | collect.","keywords":["cheapest","find","head","minimum","smallest","sort","stream"],"primaryTopics":["finding minimum","sort + head pattern"],"typicalErrors":[{"error":"E50060","correct":"      cheapest <- cat products | sort | head 1 | collect as List of Product","incorrect":"      cheapest <- products.min()","explanation":"EK9 has no .min() method. Use a stream pipeline: sort ascending, then head 1 to get the smallest."}],"companions":[]}
{"id":1087,"category":"Operators and Expressions","question":"Check if all fields in a record have been set.","url":"https://ek9.io/qa/QA1087.html","alternatePhrasings":["Test whether an object is fully initialised using ?","Determine if a record has any unset fields","In Java I'd check each field for null. How do I check if an EK9 object is set?","I need to verify that a config object has all required values before using it"],"answer":"Check if a record is set with the ? operator:\n  if full?\n    stdout.println(\"set\")\n\n'default operator ?' uses ANY-field semantics — returns true if ANY field is set. To require ALL fields, use 'override operator ?'.\n\nSee Q1089 for custom override operator ?.","ek9Example":"defines module qa.operators.checkrecordfullyset\n\n  defines class\n\n    AppConfig\n      dbHost <- String()\n      dbPort <- Integer()\n      apiKey <- String()\n\n      AppConfig()\n        ->\n          dbHost as String\n          dbPort as Integer\n          apiKey as String\n        this.dbHost :=: dbHost\n        this.dbPort :=: dbPort\n        this.apiKey :=: apiKey\n\n      AppConfig()\n        -> dbHost as String\n        this.dbHost :=: dbHost\n\n      default operator\n\n  defines program\n\n    CheckRecordFullySetDemo()\n      stdout <- Stdout()\n\n      //Fully set config\n      full <- AppConfig(\"db.prod\", 5432, \"secret-key-123\")\n      stdout.println(`Full config set?: ${full?}`)\n\n      //Partially set — only dbHost provided\n      partial <- AppConfig(\"db.dev\")\n      stdout.println(`Partial config set?: ${partial?}`)\n\n      //Empty — no fields set\n      blank <- AppConfig()\n      stdout.println(`Empty config set?: ${blank?}`)","migrationContext":"Java: manual null checks per field, or Bean Validation @NotNull. Python: all(v is not None for v in vars(obj).values()). EK9: ? operator with default (any-field) or custom (all-field) semantics.","keywords":["?","check","initialised","isSet","operator","set","validate"],"primaryTopics":["? operator","isSet checking"],"typicalErrors":[{"error":"E01073","correct":"${full?}","incorrect":"${full != null}","explanation":"EK9 has no 'null'; use the '?' operator to test whether an object is set. See ek9 -h E01073 for details."}],"companions":[]}
{"id":1088,"category":"Operators and Expressions","question":"Check which fields are set in a partially-initialised record.","url":"https://ek9.io/qa/QA1088.html","alternatePhrasings":["I have a contact with only a name filled in — verify that ? still returns true","In Java I'd check each field for null separately. Show the EK9 ANY-field semantics","Given an object with 1 of 3 fields set, confirm the default operator ? result","Test whether a partially-initialised object counts as set in EK9"],"answer":"A partially-initialised object is still 'set' under default ?:\n  partial <- ContactInfo(\"Alice\")\n  if partial?\n    stdout.println(\"set\")\n\nWith 1 of 3 fields set, default ? returns true (ANY-field semantics).\n\nSee Q1087 for basic isSet. See Q1089 for custom override operator ?.","ek9Example":"defines module qa.operators.checkpartialset\n\n  defines class\n\n    ContactInfo\n      name <- String()\n      email <- String()\n      phone <- String()\n\n      ContactInfo()\n        -> name as String\n        this.name :=: name\n        //email and phone remain unset\n\n      default operator\n\n  defines program\n\n    CheckPartialSetDemo()\n      stdout <- Stdout()\n\n      //Only name is set — email and phone remain unset\n      partial <- ContactInfo(\"Alice\")\n      stdout.println(`Contact: ${partial}`)\n\n      //default ? uses ANY-field semantics: 1 of 3 is set, so true\n      stdout.println(`Is set?: ${partial?}`)\n\n      //Compare with fully unset object\n      blank <- ContactInfo()\n      stdout.println(`Empty set?: ${blank?}`)","migrationContext":"Java: check each field != null individually. Python: hasattr or getattr checks. EK9: default ? checks any field; override ? for custom logic.","keywords":["?","check","field","individual","isSet","partial","unset"],"primaryTopics":["partial set status","ANY-field semantics"],"typicalErrors":[{"error":"E01073","correct":"${partial?}","incorrect":"${partial <> null}","explanation":"'null' does not exist in EK9 — check an object's overall set-status with the '?' operator (partial?) instead of comparing to null. See ek9 -h E01073 for details."}],"companions":[]}
{"id":1089,"category":"Operators and Expressions","question":"Write a custom override operator ? for a class with validation logic.","url":"https://ek9.io/qa/QA1089.html","alternatePhrasings":["I need my Registration to be considered valid only when name, email, and age are all present","In Java I'd write a custom isValid() method. Write the EK9 equivalent with override operator ?","Given a class with three required fields, make operator ? enforce all-field checking","Implement an override operator ? that requires ALL fields to be set before the object is usable"],"answer":"Override ? before default operator to require all fields:\n  override operator ? as pure\n    <- rtn as Boolean: name? and email? and age?\n  default operator\n\nThe override keyword is mandatory — every class inherits ? from its base type. 'default operator' must be the LAST item in the class body.\n\nSee Q1069 for mixing default with overrides. See Q1070 for which operators need override.","ek9Example":"defines module qa.operators.overrideoperatorisset\n\n  defines class\n\n    Registration\n      name <- String()\n      email <- String()\n      age <- Integer()\n\n      Registration()\n        ->\n          name as String\n          email as String\n          age as Integer\n        this.name :=: name\n        this.email :=: email\n        this.age :=: age\n\n      Registration()\n        -> name as String\n        this.name :=: name\n\n      //Custom ?: require ALL fields before default operator\n      override operator ? as pure\n        <- rtn as Boolean: name? and email? and age?\n\n      default operator\n\n  defines program\n\n    OverrideOperatorIsSetDemo()\n      stdout <- Stdout()\n\n      //Fully set — override ? returns true\n      complete <- Registration(\"Bob\", \"bob@example.com\", 30)\n      stdout.println(`Complete set?: ${complete?}`)\n\n      //Partially set — override ? returns false (not all fields set)\n      partial <- Registration(\"Alice\")\n      stdout.println(`Partial set?: ${partial?}`)","migrationContext":"Java: custom isValid() method. Python: __bool__ override. Rust: custom is_valid(). EK9: override operator ? before default operator.","keywords":["?","all fields","custom","isSet","operator","override","validate"],"primaryTopics":["override operator ?","custom isSet validation"],"typicalErrors":[],"companions":[]}
{"id":1090,"category":"Operators and Expressions","question":"Define a PaymentRequest that is only 'set' when all three fields are present.","url":"https://ek9.io/qa/QA1090.html","alternatePhrasings":["I need my payment object to reject partial data — override operator ? to require all fields","In Java I'd write an isValid() method. Write the EK9 equivalent using override operator ?","Given a class with amount, currency, and recipient, make ? return false unless all are set","Create a class where default operator generates most operators but ? has custom all-field logic"],"answer":"Custom ? goes before default operator — require all fields:\n  override operator ? as pure\n    <- rtn as Boolean: amount? and currency? and recipient?\n  default operator\n\nUse 'default operator' alone for ANY-field semantics. Add 'override operator ?' before it when ALL fields are required.\n\nSee Q1069 for mixing default with overrides. See Q1087 for default ?.","ek9Example":"defines module qa.operators.defaultvsoverrideisset\n\n  defines class\n\n    //ALL required fields needed for a valid payment\n    PaymentRequest\n      amount <- Float()\n      currency <- String()\n      recipient <- String()\n\n      PaymentRequest()\n        ->\n          amount as Float\n          currency as String\n          recipient as String\n        this.amount :=: amount\n        this.currency :=: currency\n        this.recipient :=: recipient\n\n      PaymentRequest()\n        -> amount as Float\n        this.amount :=: amount\n\n      //Custom ?: ALL fields required — BEFORE default operator\n      override operator ? as pure\n        <- rtn as Boolean: amount? and currency? and recipient?\n\n      default operator\n\n  defines program\n\n    DefaultVsOverrideIsSetDemo()\n      stdout <- Stdout()\n\n      //ALL fields set — override ? returns true\n      validPayment <- PaymentRequest(99.99, \"GBP\", \"Alice\")\n      stdout.println(`Valid payment set?: ${validPayment?}`)\n\n      //Only amount set — override ? returns false\n      partialPayment <- PaymentRequest(50.00)\n      stdout.println(`Partial payment set?: ${partialPayment?}`)","migrationContext":"Java: no equivalent — isValid() is manual. Python: __bool__ is one semantics. EK9: default ? (any-field) + optional override ? (custom logic).","keywords":["?","all-field","any-field","choose","default","isSet","override","semantics"],"primaryTopics":["default vs override operator ?","isSet design decision"],"typicalErrors":[{"error":"E01086","correct":"      override operator ? as pure\n        <- rtn as Boolean: amount? and currency? and recipient?\n\n      default operator","incorrect":"      default operator\n\n      override operator ? as pure\n        <- rtn as Boolean: x? and y?","explanation":"default operator must be the LAST item in the class body. Put override operator ? BEFORE default operator."}],"companions":[]}
{"id":1091,"category":"Operators and Expressions","question":"Provide a fallback display name when the user name is unset.","url":"https://ek9.io/qa/QA1091.html","alternatePhrasings":["I have an optional user name and need to show 'Guest' if it is missing","In Kotlin I'd use the ?: elvis operator. Write the EK9 equivalent","Given a possibly-unset string, return it if set or a default otherwise","Supply a default value for an unset variable using ?: coalescing"],"answer":"Elvis coalescing:\n  displayName <- userName ?: \"Guest\"\n\nReturns left if set, right otherwise. ?: and ?? are synonyms. See Q899 for all coalescing operators, Q1092 for coalescing min/max.","ek9Example":"defines module qa.operators.elviscoalescing\n\n  defines program\n\n    ElvisCoalescingDemo()\n      stdout <- Stdout()\n\n      //User name is unset — fallback to \"Guest\"\n      userName <- String()\n      displayName <- userName ?: \"Guest\"\n      stdout.println(`Display: ${displayName}`)\n\n      //User name is set — use the actual value\n      knownUser <- \"Alice\"\n      displayName2 <- knownUser ?: \"Guest\"\n      stdout.println(`Display: ${displayName2}`)","migrationContext":"Kotlin: value ?: default. JavaScript: value ?? default. Swift: value ?? default. C#: value ?? default. EK9: value ?: default or value ?? default.","keywords":["?:","??","coalescing","default","elvis","fallback","unset"],"primaryTopics":["?: elvis operator","value coalescing"],"typicalErrors":[{"error":"E01073","correct":"displayName <- userName ?: \"Guest\"","incorrect":"displayName <- userName ?: null","explanation":"EK9 has no null; use tri-state (unset/set) values with the ?: coalescing operator for a fallback. See ek9 -h E01073 for details."}],"companions":[]}
{"id":1092,"category":"Operators and Expressions","question":"Get the smaller of two prices, even if one might be unset.","url":"https://ek9.io/qa/QA1092.html","alternatePhrasings":["I have two price values and one could be missing — find the minimum safely","In Java I'd need null checks before Math.min(). Write the safe EK9 version","Given two optional sensor readings, return the lower one using <? coalescing","Select the minimum of two values with automatic unset handling"],"answer":"Coalescing min (<?) returns the smaller value, max (>?) returns the larger:\n\n  cheapest <- priceA <? priceB\n  topScore <- scoreA >? scoreB\n\n  //With one unset — returns the set one\n  knownPrice <- 49.99\n  unknownPrice <- Float()\n  best <- knownPrice <? unknownPrice\n  //best is 49.99 — the set value wins\n\n<? and >? are unique to EK9. If both are set, returns the smaller/larger. If one is unset, returns the set one. If both unset, result is unset.\n\nSee Q899 for all coalescing operators. See Q1091 for ?: elvis.","ek9Example":"defines module qa.operators.minmaxcoalescing\n\n  defines function\n\n    cheaperOf() as pure\n      ->\n        left as Float\n        right as Float\n      <- rtn as Float: left <? right\n\n    higherOf() as pure\n      ->\n        left as Integer\n        right as Integer\n      <- rtn as Integer: left >? right\n\n  defines program\n\n    MinMaxCoalescingDemo()\n      stdout <- Stdout()\n\n      //Both set — returns the smaller\n      cheapest <- cheaperOf(29.99, 19.99)\n      stdout.println(`Cheapest: ${cheapest}`)\n\n      //One unset — returns the set one\n      knownPrice <- 49.99\n      unknownPrice <- Float()\n      best <- knownPrice <? unknownPrice\n      stdout.println(`Best available: ${best}`)\n\n      //Coalescing maximum — both set, returns the larger\n      topScore <- higherOf(85, 92)\n      stdout.println(`Top score: ${topScore}`)","migrationContext":"Java: a == null ? b : b == null ? a : Math.min(a, b). Python: min(x for x in [a, b] if x is not None). EK9: a <? b — one operator handles all cases.","keywords":["<?",">?","coalescing","maximum","minimum","safe","unset"],"primaryTopics":["<? coalescing minimum",">? coalescing maximum"],"typicalErrors":[{"error":"E50001","correct":"knownPrice <? unknownPrice","incorrect":"Math.min(knownPrice, unknownPrice)","explanation":"EK9 has no Math type, so Math.min(...) cannot be resolved — use the <? coalescing minimum operator instead. See ek9 -h E50001 for details."}],"companions":[]}
{"id":1093,"category":"Operators and Expressions","question":"Chain multiple fallback values to find the first one that is set.","url":"https://ek9.io/qa/QA1093.html","alternatePhrasings":["I have three config sources and need the first available value from any of them","In JavaScript I'd chain ?? operators. Write the EK9 multi-level fallback","Given primary, secondary, and default values, return the first set one","Cascade through multiple optional values using chained ?: operators"],"answer":"Chained fallback:\n  port <- envPort ?: configPort ?: defaultPort\n\nEvaluates left-to-right, returning the first set value in the chain. Works with any number of levels. See Q1091 for basic ?: coalescing, Q899 for all coalescing operators.","ek9Example":"defines module qa.operators.chainedcoalescing\n\n  defines program\n\n    ChainedCoalescingDemo()\n      stdout <- Stdout()\n\n      //First two unset — falls through to the third\n      envPort <- Integer()\n      configPort <- Integer()\n      defaultPort <- 8080\n\n      port <- envPort ?: configPort ?: defaultPort\n      stdout.println(`Port: ${port}`)\n\n      //Second is set — stops there\n      envHost <- String()\n      configHost <- \"config.local\"\n      defaultHost <- \"localhost\"\n\n      host <- envHost ?: configHost ?: defaultHost\n      stdout.println(`Host: ${host}`)","migrationContext":"JavaScript: a ?? b ?? c. Kotlin: a ?: b ?: c. Swift: a ?? b ?? c. EK9: a ?: b ?: c — identical chaining syntax.","keywords":["?:","cascade","chained","coalescing","fallback","levels","multiple"],"primaryTopics":["chained coalescing","multi-level fallback"],"typicalErrors":[{"error":"E01073","correct":"port <- envPort ?: configPort ?: defaultPort","incorrect":"port <- envPort ?: configPort ?: null","explanation":"EK9 has no 'null' - using it (here as a fallback value) is rejected with E01073; chain ?: over real set/unset values instead. See ek9 -h E01073 for details."}],"companions":[]}
{"id":1094,"category":"Operators and Expressions","question":"Convert a Customer record to both display string and JSON format.","url":"https://ek9.io/qa/QA1094.html","alternatePhrasings":["I need to print a record as a readable string and also serialise it to JSON","In Java I'd override toString() and write a toJson(). Write the EK9 equivalent","Given a class with name and email, produce $ string and $$ JSON representations","Generate both human-readable and JSON output from a single record"],"answer":"String and interpolation:\n  str <- $cust\n  stdout.println(`Customer: ${cust}`)\n\nDefault operator $ generates ClassName(field=value) format. Works directly in backtick interpolation. See Q908 for $ string operator, Q887 for $$ JSON operator.","ek9Example":"defines module qa.operators.recordtostringjson\n\n  defines class\n\n    Customer\n      name <- String()\n      email <- String()\n      age <- Integer()\n\n      Customer()\n        ->\n          name as String\n          email as String\n          age as Integer\n        this.name :=: name\n        this.email :=: email\n        this.age :=: age\n\n      default operator\n\n  defines program\n\n    RecordToStringJsonDemo()\n      stdout <- Stdout()\n\n      cust <- Customer(\"Alice\", \"alice@example.com\", 30)\n\n      //$ string representation\n      str <- $cust\n      stdout.println(`String: ${str}`)\n\n      //In backtick interpolation\n      stdout.println(`Customer: ${cust}`)","migrationContext":"Java: toString() and manual JSON. Python: __str__ and json.dumps(). Rust: Display + serde. EK9: default operator $ and default operator $$ — one line each.","keywords":["$","$$","JSON","convert","display","serialise","string"],"primaryTopics":["$ string operator","$$ JSON operator"],"typicalErrors":[{"error":"E50060","correct":"      str <- $cust","incorrect":"      str <- cust.toString()","explanation":"EK9 has no Java-style '.toString()' method; use the '$' prefix operator for string conversion. See ek9 -h E50060 for details."}],"companions":[]}
{"id":1095,"category":"Operators and Expressions","question":"Use a Coordinate record as a Dict key.","url":"https://ek9.io/qa/QA1095.html","alternatePhrasings":["I need to store values in a Dict keyed by a custom class — what operators are required?","In Java I'd implement hashCode() and equals(). Write the EK9 equivalent for Dict keys","Given a Coordinate class, set up the operators needed to use it as a Dict key","Make a custom type usable as a dictionary key with #? and == operators"],"answer":"Using a record as a Dict key:\n  grid <- Dict() of (Coordinate, String)\n  grid += DictEntry(Coordinate(0, 0), \"origin\")\n\nDict keys need #? (hashcode) and == (equality). Use 'default operator' to generate both from declared fields, ensuring the hash contract holds automatically. See Q966 for hashcode details, Q1083 for equality.","ek9Example":"defines module qa.operators.recordasdictkey\n\n  defines class\n\n    Coordinate\n      x <- Integer()\n      y <- Integer()\n\n      Coordinate()\n        ->\n          x as Integer\n          y as Integer\n        this.x :=: x\n        this.y :=: y\n\n      default operator\n\n  defines program\n\n    RecordAsDictKeyDemo()\n      stdout <- Stdout()\n\n      //Use Coordinate as Dict key — needs (K, V) parentheses\n      grid <- Dict() of (Coordinate, String)\n      grid += DictEntry(Coordinate(0, 0), \"origin\")\n      grid += DictEntry(Coordinate(1, 0), \"east\")\n      grid += DictEntry(Coordinate(0, 1), \"north\")\n\n      stdout.println(`Grid entries: ${grid.length()}`)\n\n      //Look up by equal key using getOrDefault\n      lookup <- Coordinate(1, 0)\n      result <- grid.getOrDefault(lookup, \"unknown\")\n      stdout.println(`Found: ${result}`)","migrationContext":"Java: implement hashCode() and equals(). Python: __hash__ and __eq__. Rust: Hash + PartialEq. EK9: default operator generates #? and == from fields.","keywords":["#?","==","Dict","equality","hashcode","key","map"],"primaryTopics":["Dict key requirements","#? and == for Dict keys"],"typicalErrors":[{"error":"E07235","correct":"      default operator","incorrect":"      hashCode()\n        <- rtn as Integer: x * 31 + y","explanation":"EK9 has no hashCode() method. Use 'default operator #?' or 'default operator' to generate hashcode from fields."}],"companions":[]}
{"id":1096,"category":"Operators and Expressions","question":"Verify that two equal objects produce the same hashcode.","url":"https://ek9.io/qa/QA1096.html","alternatePhrasings":["I need to confirm the hash contract holds for my custom class","In Java I'd test that hashCode() is consistent with equals(). Show the EK9 equivalent","Given two objects with identical field values, check their #? hashcodes match","Test hashcode consistency between equal objects using #?"],"answer":"Hashcode prefix operator:\n  h1 <- #?p1\n  h2 <- #?p2\n  assert h1 == h2\n\nEqual objects must have equal hashes. 'default operator' generates #? and == from the same fields, ensuring consistency. See Q966 for hashcode details, Q1095 for using records as Dict keys.","ek9Example":"defines module qa.operators.hashcodeoperator\n\n  defines class\n\n    Point\n      x <- Integer()\n      y <- Integer()\n\n      Point()\n        ->\n          x as Integer\n          y as Integer\n        this.x :=: x\n        this.y :=: y\n\n      default operator\n\n  defines program\n\n    HashcodeOperatorDemo()\n      stdout <- Stdout()\n\n      p1 <- Point(10, 20)\n      p2 <- Point(10, 20)\n      p3 <- Point(99, 20)\n\n      //Equal objects must have equal hashcodes\n      h1 <- #?p1\n      h2 <- #?p2\n      stdout.println(`p1 hash: ${h1}`)\n      stdout.println(`p2 hash: ${h2}`)\n\n      if h1 == h2\n        stdout.println(\"Hash contract holds: equal objects, equal hashes\")\n\n      //Different objects likely have different hashes\n      h3 <- #?p3\n      stdout.println(`p3 hash: ${h3}`)","migrationContext":"Java: obj.hashCode(). Python: hash(obj). Rust: Hash trait. EK9: #?obj prefix operator — no method call needed.","keywords":["#?","consistency","contract","equal","hash","hashcode"],"primaryTopics":["#? hashcode operator","hash contract"],"typicalErrors":[{"error":"E50060","correct":"h1 <- #?p1","incorrect":"h1 <- p1.hashCode()","explanation":"EK9 uses the '#?' prefix operator for hashcodes, not a '.hashCode()' method — 'p1.hashCode()' does not resolve. See ek9 -h E50060 for details."}],"companions":[]}
{"id":1097,"category":"Operators and Expressions","question":"Create a Product class with full operator support in one line.","url":"https://ek9.io/qa/QA1097.html","alternatePhrasings":["I need a class with copy, compare, equals, string, hash — generate them all automatically","In Kotlin I'd use a data class. Write the EK9 equivalent with default operator","Given a class with name and price fields, add all operators without writing each one","Set up a complete class with 'default operator' to generate everything"],"answer":"One line generates all operators:\n  default operator\n\nMust be the last item in the class body. Generates ==, <>, <, >, <=, >=, <=>, $, $$, #?, :=:, :~:, :^:, ? from declared fields. See Q949 for default operator rules, Q1098 for overriding individual operators after default.","ek9Example":"defines module qa.operators.definerecorddefaultops\n\n  defines class\n\n    Product\n      name <- String()\n      price <- Float()\n\n      Product()\n        ->\n          name as String\n          price as Float\n        this.name :=: name\n        this.price :=: price\n\n      //One line generates all operators\n      default operator\n\n  defines program\n\n    DefineRecordDefaultOpsDemo()\n      stdout <- Stdout()\n\n      a <- Product(\"Widget\", 9.99)\n      b <- Product(\"Widget\", 9.99)\n      c <- Product(\"Gadget\", 29.99)\n\n      //$ string\n      stdout.println(`Product: ${a}`)\n\n      //== equality\n      if a == b\n        stdout.println(\"a and b are equal\")\n\n      //<=> comparison\n      if a < c\n        stdout.println(\"Widget is cheaper than Gadget\")\n\n      //:=: copy\n      backup <- Product()\n      backup :=: a\n      stdout.println(`Backup: ${backup}`)\n\n      //#? hashcode\n      stdout.println(`Hash: ${#?a}`)","migrationContext":"Kotlin: data class Product(val name: String, val price: Float). Java: record Product(String name, float price). EK9: class with 'default operator' as last line.","keywords":["all","automatic","default operator","generate","one line","operators"],"primaryTopics":["default operator","operator generation"],"typicalErrors":[{"error":"E01086","correct":"      default operator","incorrect":"      default operator\n\n      someMethod() as pure\n        <- rtn as String: \"oops\"","explanation":"'default operator' must be the LAST item in a class body — no methods or fields may follow it. See ek9 -h E01086 for details."}],"companions":[]}
{"id":1098,"category":"Operators and Expressions","question":"Generate all operators with default, but customise the string representation.","url":"https://ek9.io/qa/QA1098.html","alternatePhrasings":["I want default operator for everything except $ which I need to customise","In Java I'd auto-generate equals/hashCode but override toString(). Write the EK9 way","Given a child class, override the parent's default $ with a custom format","Use default operator for most operators but provide a custom override operator $"],"answer":"Override before default:\n  override operator $ as pure\n    <- rtn as String: `Custom(age=${age})`\n  default operator\n\nOverrides go BEFORE 'default operator', which must be last in the class body. See Q1069 for mixing default with overrides, Q1097 for default operator basics.","ek9Example":"defines module qa.operators.overrideafterdefault\n\n  defines class\n\n    Base as open\n      name as String: String()\n\n      Base()\n        -> name as String\n        this.name :=? name\n\n      default operator\n\n    Child extends Base\n      age as Integer: Integer()\n\n      Child()\n        ->\n          childName as String\n          age as Integer\n        super(childName)\n        this.age :=? age\n\n      //Custom $ BEFORE default operator\n      override operator $ as pure\n        <- rtn as String: `Child(age=${age})`\n\n      default operator\n\n  defines program\n\n    OverrideAfterDefaultDemo()\n      stdout <- Stdout()\n\n      base <- Base(\"ParentObj\")\n      stdout.println($base)\n\n      child <- Child(\"ChildObj\", 10)\n      stdout.println($child)","migrationContext":"Java: Lombok @Data + manual toString(). Kotlin: data class + override fun toString(). EK9: override operator $ before default operator.","keywords":["$","combine","customise","default operator","override","string"],"primaryTopics":["override before default operator","custom $ with default"],"typicalErrors":[{"error":"E01086","correct":"      override operator $ as pure\n        <- rtn as String: `Child(age=${age})`\n","incorrect":"      default operator\n      override operator $ as pure\n        <- rtn as String: `Custom`","explanation":"default operator must be LAST. Put override operators BEFORE it."}],"companions":[]}
{"id":1099,"category":"Operators and Expressions","question":"Check if a greeting string contains the word 'world'.","url":"https://ek9.io/qa/QA1099.html","alternatePhrasings":["I need to test whether a string includes a specific substring","In Java I'd use str.contains(). Write the EK9 substring check","Given a message string, verify it contains a keyword and also check for absence","Search for a substring inside a string using the contains operator"],"answer":"Substring check:\n  hasWorld <- greeting contains \"world\"\n  noXyz <- greeting not contains \"xyz\"\n\nKeyword operator, not a method call. Returns Boolean. See Q1101 for 'is in' collection membership.","ek9Example":"defines module qa.operators.containsnotcontains\n\n  defines program\n\n    ContainsNotContainsDemo()\n      stdout <- Stdout()\n\n      greeting <- \"hello world\"\n\n      //Check substring is present\n      hasWorld <- greeting contains \"world\"\n      stdout.println(`Contains 'world': ${hasWorld}`)\n\n      //Check substring is absent\n      noXyz <- greeting not contains \"xyz\"\n      stdout.println(`Not contains 'xyz': ${noXyz}`)","migrationContext":"Java: str.contains(sub). Python: sub in str. Rust: str.contains(sub). Go: strings.Contains(). EK9: str contains sub — keyword, not method.","keywords":["check","contains","not contains","search","string","substring"],"primaryTopics":["contains operator","substring detection"],"typicalErrors":[],"companions":[]}
{"id":1100,"category":"Operators and Expressions","question":"Check if an email string matches a basic pattern.","url":"https://ek9.io/qa/QA1100.html","alternatePhrasings":["I need to validate a string against a regex pattern in EK9","In Python I'd use re.match(). Write the EK9 regex check","Given a user input string, test it against a regular expression","Validate string format using the matches operator and a regex literal"],"answer":"Regex matching:\n  valid <- email matches /[a-zA-Z]+@[a-zA-Z]+\\.[a-zA-Z]+/\n\nRegex literals use /slashes/. Returns Boolean. Use 'not matches' for negation. See Q1099 for contains (substring), Q1101 for is in (collection membership).","ek9Example":"defines module qa.operators.matchesregex\n\n  defines program\n\n    MatchesRegexDemo()\n      stdout <- Stdout()\n\n      email <- \"alice@example.com\"\n\n      //Match against a basic email pattern\n      valid <- email matches /[a-zA-Z]+@[a-zA-Z]+\\.[a-zA-Z]+/\n      stdout.println(`Valid email: ${valid}`)\n\n      //Non-matching input\n      badInput <- \"not-an-email\"\n      invalid <- badInput matches /[a-zA-Z]+@[a-zA-Z]+\\.[a-zA-Z]+/\n      stdout.println(`Bad input valid: ${invalid}`)","migrationContext":"Java: Pattern.matches(regex, str). Python: re.match(pattern, str). Rust: Regex::new(pat).is_match(str). EK9: str matches /pattern/ — inline regex literal.","keywords":["format","matches","pattern","regex","regular expression","validate"],"primaryTopics":["matches operator","regex validation"],"typicalErrors":[{"error":"E50060","correct":"email matches /[a-zA-Z]+@[a-zA-Z]+\\.[a-zA-Z]+/","incorrect":"email.matches(\"[a-zA-Z]+@[a-zA-Z]+\")","explanation":"'matches' takes a RegEx literal (/pattern/), so calling email.matches(\"...\") with a String argument does not resolve. See ek9 -h E50060 for details."}],"companions":[]}
{"id":1101,"category":"Operators and Expressions","question":"Check if 'banana' is in a list of approved fruits.","url":"https://ek9.io/qa/QA1101.html","alternatePhrasings":["I have a list of allowed values and need to test membership","In Python I'd use 'if item in list'. Write the EK9 equivalent","Given a list of strings, check whether a specific value is present","Test collection membership using the 'is in' expression"],"answer":"Collection membership:\n  found <- \"banana\" is in fruits\n\nReverse of 'contains' (which checks substrings). Use 'not in' for negation. Returns Boolean. See Q1099 for contains (substring), Q1102 for is in with ranges.","ek9Example":"defines module qa.operators.isinlist\n\n  defines program\n\n    IsInListDemo()\n      stdout <- Stdout()\n\n      fruits <- List() of String\n      fruits += \"apple\"\n      fruits += \"banana\"\n      fruits += \"cherry\"\n\n      //Check membership\n      found <- \"banana\" is in fruits\n      stdout.println(`banana in list: ${found}`)\n\n      //Check absence\n      missing <- \"grape\" is in fruits\n      stdout.println(`grape in list: ${missing}`)","migrationContext":"Python: item in list. Java: list.contains(item). Rust: list.contains(&item). Go: manual loop. EK9: item is in list — keyword expression.","keywords":["check","collection","contains","is in","list","membership"],"primaryTopics":["is in expression","collection membership"],"typicalErrors":[],"companions":[]}
{"id":1102,"category":"Operators and Expressions","question":"Check if a score falls within the range 1 to 10.","url":"https://ek9.io/qa/QA1102.html","alternatePhrasings":["I need to validate that a number is between a minimum and maximum","In Python I'd use 'if 1 <= x <= 10'. Write the EK9 range check","Given a user input, verify it falls within an allowed range using 'is in'","Test whether an integer is within a start ... end range"],"answer":"Range membership:\n  valid <- score is in 1 ... 10\n\nInclusive on both ends. Works with any comparable type (Integer, Float, Character, Date). Bounds can be variables. See Q1101 for is in with collections, Q1099 for contains (substring).","ek9Example":"defines module qa.operators.isinrange\n\n  defines program\n\n    IsInRangeDemo()\n      stdout <- Stdout()\n\n      upperBound <- 10\n\n      //Inside the range\n      inside <- 5 is in 1 ... upperBound\n      stdout.println(`5 in 1..10: ${inside}`)\n\n      //Outside the range\n      outside <- 15 is in 1 ... upperBound\n      stdout.println(`15 in 1..10: ${outside}`)\n\n      //On the boundary (inclusive)\n      boundary <- 1 is in 1 ... upperBound\n      stdout.println(`1 in 1..10: ${boundary}`)","migrationContext":"Python: 1 <= x <= 10. Java: x >= 1 && x <= 10. Rust: (1..=10).contains(&x). Go: x >= 1 && x <= 10. EK9: x is in 1 ... 10.","keywords":["between","bounds","inclusive","is in","range","validate"],"primaryTopics":["range membership","is in with ranges"],"typicalErrors":[],"companions":[]}
{"id":1103,"category":"Operators and Expressions","question":"Increment an Integer, a Float, and a Character value.","url":"https://ek9.io/qa/QA1103.html","alternatePhrasings":["I need to advance a counter, increase a float, and step a character to the next letter","In Java I'd use ++ on an int. Show EK9 increment on different types","Given an Integer, Float, and Character, apply ++ and -- to each","Use the increment and decrement operators across multiple EK9 types"],"answer":"Increment and decrement:\n  count++\n  letter++\n\nStatement-only operators -- cannot be used in expressions. Works on Integer, Float, Character, and other ordered types. See Q967 for increment/decrement rules, Q780 for statement-only restriction.","ek9Example":"defines module qa.operators.incrementoperators\n\n  defines program\n\n    IncrementOperatorsDemo()\n      stdout <- Stdout()\n\n      //Integer increment\n      count <- 10\n      count++\n      stdout.println(`After ++: ${count}`)\n      count--\n      stdout.println(`After --: ${count}`)\n\n      //Float increment\n      price <- 9.99\n      price++\n      stdout.println(`Float after ++: ${price}`)\n\n      //Character increment — advances to next code point\n      letter <- 'a'\n      letter++\n      stdout.println(`'a' after ++: ${letter}`)\n\n      //Multiple increments\n      digit <- 'A'\n      digit++\n      digit++\n      digit++\n      stdout.println(`'A' after +++: ${digit}`)","migrationContext":"Java: count++ (expression). C: count++ (expression). EK9: count++ (statement ONLY — cannot use in expressions like x = count++).","keywords":["++","--","Character","Float","Integer","decrement","increment"],"primaryTopics":["++ increment","-- decrement","statement operators"],"typicalErrors":[{"error":"E07950","correct":"      count++","incorrect":"      result <- count++","explanation":"++ and -- are statement-only operators in EK9. They cannot be used in expressions or assignments."}],"companions":[]}
{"id":1104,"category":"Operators and Expressions","question":"Get the absolute value of -42 and the square root of 16.","url":"https://ek9.io/qa/QA1104.html","alternatePhrasings":["I need to remove the sign from a negative number and compute a square root","In Java I'd use Math.abs() and Math.sqrt(). Write the EK9 equivalents","Given a negative integer and a float, apply abs and sqrt prefix operators","Compute absolute value and square root using EK9's unary prefix operators"],"answer":"Prefix unary operators:\n  positive <- abs negVal\n  side <- sqrt area\n\nKeyword operators, not method calls -- no parentheses needed. abs works on Integer and Float; sqrt on Float. See Q240 for arithmetic operators.","ek9Example":"defines module qa.operators.unaryabssqrt\n\n  defines program\n\n    UnaryAbsSqrtDemo()\n      stdout <- Stdout()\n\n      //Absolute value of a negative integer\n      negVal <- -42\n      positive <- abs negVal\n      stdout.println(`abs -42: ${positive}`)\n\n      //Square root of a float\n      area <- 16.0\n      side <- sqrt area\n      stdout.println(`sqrt 16.0: ${side}`)\n\n      //Absolute value of a negative float\n      temperature <- -5.7\n      magnitude <- abs temperature\n      stdout.println(`abs -5.7: ${magnitude}`)","migrationContext":"Java: Math.abs(x), Math.sqrt(x). Python: abs(x), math.sqrt(x). Rust: x.abs(), x.sqrt(). EK9: abs x, sqrt x — prefix operators, not functions.","keywords":["abs","absolute","math","prefix","sqrt","square root","unary"],"primaryTopics":["abs operator","sqrt operator","prefix operators"],"typicalErrors":[{"error":"E50001","correct":"      positive <- abs negVal","incorrect":"      positive <- Math.abs(negVal)","explanation":"EK9 has no Math class; `Math.abs(negVal)` leaves 'Math' unresolved and triggers E50001 - use the 'abs' prefix operator: abs negVal. See ek9 -h E50001 for details."}],"companions":[]}
{"id":1105,"category":"Dependency Injection","question":"Create a Logger component with a name field and a log method.","url":"https://ek9.io/qa/QA1105.html","alternatePhrasings":["I need to define a reusable service component with fields and methods","In Spring I'd use @Component on a class. Write the EK9 equivalent","Given a logging service, define it as a component with state and behaviour","Build a basic component that holds configuration and provides a method"],"answer":"Define a component with fields and methods:\n\n  defines component\n    Logger\n      name <- \"DefaultLogger\"\n      log()\n        -> message as String\n        <- output as String: name + \": \" + message\n\nComponents are DI-managed types with fields, constructors, methods, and operators.\n\nSee Q227 for compile-time DI validation. See Q1110 for register and inject.","ek9Example":"defines module qa.di.definecomponent\n\n  defines component\n\n    Logger\n      name <- \"AppLogger\"\n\n      getName()\n        <- rtn as String: name\n\n      log()\n        -> message as String\n        <- output as String: `${name}: ${message}`\n\n      default operator\n\n  defines program\n\n    DefineComponentDemo()\n      stdout <- Stdout()\n\n      logger <- Logger()\n      result <- logger.log(\"Application started\")\n      stdout.println(result)\n      stdout.println(`Logger name: ${logger.getName()}`)","migrationContext":"Spring: @Component class. Guice: bind(Logger.class). .NET: services.AddSingleton<Logger>(). EK9: defines component + register in application.","keywords":["DI","component","define","fields","methods","service"],"primaryTopics":["defines component","component basics"],"typicalErrors":[{"error":"E01010","correct":"defines component\n\n    Logger","incorrect":"  defines class\n    @Component Logger","explanation":"EK9 has no annotations. Use 'defines component' to declare DI-managed types."}],"companions":[],"oracleToolHint":{"tool":"ek9_scaffold","intent":"component","description":"Oracle can generate a component with fields and methods for dependency injection."}}
{"id":1106,"category":"Dependency Injection","question":"Copy a component and check if the copy equals the original.","url":"https://ek9.io/qa/QA1106.html","alternatePhrasings":["I need to duplicate a component and verify the copy matches","In Java I'd clone a service bean. Write the EK9 component copy","Given a Logger component, create a copy using :=: and test equality with ==","Duplicate a component's state into a new instance"],"answer":"Copy and compare components using :=: and ==:\n\n  copy <- Logger()\n  copy :=: original\n  if copy == original\n    stdout.println(\"Match\")\n\n'default operator' generates :=: and == from component fields, same as for classes.\n\nSee Q1105 for defining components. See Q1075 for :=: copy details.","ek9Example":"defines module qa.di.componentcopyequality\n\n  defines component\n\n    Config\n      host <- \"localhost\"\n      port <- 8080\n\n      getHost()\n        <- rtn as String: host\n\n      getPort()\n        <- rtn as Integer: port\n\n      default operator\n\n  defines program\n\n    ComponentCopyEqualityDemo()\n      stdout <- Stdout()\n\n      original <- Config()\n      stdout.println(`Original: ${original}`)\n\n      //Copy\n      backup <- Config()\n      backup :=: original\n      stdout.println(`Copy: ${backup}`)\n\n      //Equality\n      if original == backup\n        stdout.println(\"Original and copy are equal\")","migrationContext":"Java: no standard bean cloning. Spring: prototype scope creates new instances. EK9: :=: copies component field values directly.","keywords":[":=:","==","component","copy","duplicate","equality"],"primaryTopics":["component copy","component equality"],"typicalErrors":[{"error":"E50060","correct":"backup :=: original","incorrect":"backup := original.clone()","explanation":"EK9 has no .clone() method; use ':=:' to copy a component's fields. See ek9 -h E50060 for details."}],"companions":[]}
{"id":1107,"category":"Dependency Injection","question":"Check the set and unset states of a component's fields.","url":"https://ek9.io/qa/QA1107.html","alternatePhrasings":["I need to verify which fields in a component have been initialised","In Java I'd check for null fields on a bean. Show the EK9 component isSet pattern","Given a component with default values, confirm operator ? reflects field state","Test whether a component is set when its fields have initial values"],"answer":"Check component set status with ?:\n\n  settings <- Settings()\n  if settings?\n    stdout.println(\"Settings ready\")\n\nField initialisers (e.g., name <- \"default\") make the component SET immediately after construction.\n\nSee Q1087 for default operator ? semantics. See Q1105 for defining components.","ek9Example":"defines module qa.di.componentissetstates\n\n  defines component\n\n    Settings\n      theme <- \"dark\"\n      fontSize <- 14\n\n      getTheme()\n        <- rtn as String: theme\n\n      default operator\n\n  defines program\n\n    ComponentIsSetStatesDemo()\n      stdout <- Stdout()\n\n      //Component with field initialisers — set immediately\n      settings <- Settings()\n      stdout.println(`Settings set?: ${settings?}`)\n      stdout.println(`Settings: ${settings}`)","migrationContext":"Java: manual null checks on bean fields. Python: hasattr checks. EK9: operator ? on the component — any-field semantics by default.","keywords":["?","component","fields","initialised","isSet","state"],"primaryTopics":["component isSet","component field state"],"typicalErrors":[{"error":"E01073","correct":"settings?","incorrect":"settings != null","explanation":"EK9 has no null; use the ? operator to test whether a component has meaningful data instead of comparing to null. See ek9 -h E01073 for details."}],"companions":[]}
{"id":1108,"category":"Dependency Injection","question":"Extend a BaseService component and add a version field to the child.","url":"https://ek9.io/qa/QA1108.html","alternatePhrasings":["I have a base component and need to create a specialised version with extra fields","In Spring I'd extend a @Service class. Write the EK9 component inheritance","Given a BaseService, create an ExtendedService that adds version tracking","Build a component hierarchy with parent and child fields"],"answer":"Mark parent 'as open' and extend with child:\n\n  BaseService as open\n    serviceName <- \"Base\"\n    default operator\n\n  ExtendedService extends BaseService\n    version <- 1\n    default operator\n\nChild inherits parent fields. 'default operator' covers both parent and child fields.\n\nSee Q1105 for basic components. See Q1080 for class inheritance with :=: copy.","ek9Example":"defines module qa.di.componentinheritance\n\n  defines component\n\n    BaseService as open\n      serviceName <- \"BaseService\"\n\n      default operator\n\n  defines component\n\n    ExtendedService extends BaseService\n      version <- 1\n\n      default operator\n\n  defines program\n\n    ComponentInheritanceDemo()\n      stdout <- Stdout()\n\n      base <- BaseService()\n      stdout.println(`Base: ${base}`)\n\n      ext <- ExtendedService()\n      stdout.println(`Extended: ${ext}`)\n      stdout.println(`Extended set?: ${ext?}`)","migrationContext":"Spring: extend a @Component class. Guice: bind subclass to parent interface. EK9: component extends with 'as open' on parent.","keywords":["child","component","extends","inheritance","open","parent"],"primaryTopics":["component inheritance","extends component"],"typicalErrors":[{"error":"E05030","correct":"    BaseService as open","incorrect":"    BaseService","explanation":"Components are closed by default. Add 'as open' to allow extension."}],"companions":[]}
{"id":1109,"category":"Dependency Injection","question":"Mutate a Counter component's state through increment and reset methods.","url":"https://ek9.io/qa/QA1109.html","alternatePhrasings":["I need a component that tracks a count and provides methods to change it","In Java I'd have a mutable bean with setter methods. Write the EK9 equivalent","Given a counter component, call methods that modify its internal state","Build a stateful component and demonstrate state changes through method calls"],"answer":"Non-pure methods modify component state:\n\n  increment()\n    count++\n  reset()\n    count: 0\n\nPure methods (readers) use 'as pure'; non-pure methods (writers) do not. The compiler enforces this.\n\nSee Q1105 for basic components. See Q1106 for copying component state.","ek9Example":"defines module qa.di.componentmutation\n\n  defines component\n\n    Counter\n      count <- 0\n\n      increment()\n        count++\n\n      reset()\n        count: 0\n\n      getCount() as pure\n        <- rtn as Integer: count\n\n      default operator\n\n  defines program\n\n    ComponentMutationDemo()\n      stdout <- Stdout()\n\n      counter <- Counter()\n      stdout.println(`Initial: ${counter.getCount()}`)\n\n      counter.increment()\n      counter.increment()\n      counter.increment()\n      stdout.println(`After 3 increments: ${counter.getCount()}`)\n\n      counter.reset()\n      stdout.println(`After reset: ${counter.getCount()}`)","migrationContext":"Java: mutable service with fields and methods. Spring: @Service with state. EK9: component with mutable fields and non-pure methods.","keywords":["component","increment","method","mutable","mutation","state"],"primaryTopics":["component mutation","mutable component state"],"typicalErrors":[{"error":"E08120","correct":"      increment()\n        count++","incorrect":"      increment() as pure\n        count++","explanation":"Methods that modify state cannot be marked 'as pure'. Remove 'as pure' from mutating methods."}],"companions":[]}
{"id":1110,"category":"Dependency Injection","question":"Register a Logger service and inject it into a program.","url":"https://ek9.io/qa/QA1110.html","alternatePhrasings":["I need to wire a component into a program using EK9's DI system","In Spring I'd use @Autowired to inject a service. Write the EK9 equivalent","Given an abstract Logger and concrete ConsoleLogger, register and inject it","Set up an application that provides a Logger to a program via injection"],"answer":"Register concrete as abstract, inject with ! suffix:\n\n  register ConsoleLogger() as Logger\n  ...\n  service as Logger!\n\nThe ! suffix marks an injection point. The compiler validates a matching registration exists.\n\nSee Q227 for compile-time DI validation. See Q228 for registration ordering.","ek9Example":"defines module qa.di.registerinjectbasic\n\n  defines component\n\n    Logger as abstract\n      log() as abstract\n        -> message as String\n        <- output as String?\n\n      default operator ?\n\n    ConsoleLogger is Logger\n      override log()\n        -> message as String\n        <- output as String: `LOG: ${message}`\n\n      default operator ?\n\n  defines application\n\n    BasicApp\n      register ConsoleLogger() as Logger\n\n  defines program\n\n    RegisterInjectBasicDemo() with application of BasicApp\n      stdout <- Stdout()\n\n      //Injected via '!' suffix — compiler verified\n      logger as Logger!\n      result <- logger.log(\"Application started\")\n      stdout.println(result)","migrationContext":"Spring: @Autowired Logger logger. Guice: @Inject Logger logger. .NET: services.AddSingleton<ILogger, ConsoleLogger>(). EK9: register + '!' suffix injection.","keywords":["!","DI","application","component","inject","register","wire"],"primaryTopics":["register and inject","DI wiring"],"typicalErrors":[{"error":"E08210","correct":"      register ConsoleLogger() as Logger","incorrect":"      //missing registration","explanation":"Every injection point (!) must have a matching registration in the application. The compiler rejects missing registrations."}],"companions":[],"oracleToolHint":{"tool":"ek9_scaffold","intent":"application","description":"Oracle can generate an application with component registration and program injection wiring."}}
{"id":1111,"category":"Dependency Injection","question":"Register dependencies in the correct order so a service can inject them.","url":"https://ek9.io/qa/QA1111.html","alternatePhrasings":["I need to ensure my Logger is registered before the MessageService that depends on it","In Spring ordering is automatic. Show the EK9 explicit registration ordering","Given three components with dependencies, register them in dependency order","Wire components so that dependencies are available before dependents are created"],"answer":"Register dependencies before the components that inject them:\n\n  register ConsoleLogger() as Logger\n  register UpperFormatter() as Formatter\n  register SimpleService() as Service\n\nThe compiler validates ordering: Logger must be registered before any component that injects Logger!.\n\nSee Q228 for ordering rules. See Q229 for circular dependency detection.","ek9Example":"defines module qa.di.injectorder\n\n  defines component\n\n    Logger as abstract\n      log() as abstract\n        -> message as String\n        <- output as String?\n      default operator ?\n\n    ConsoleLogger is Logger\n      override log()\n        -> message as String\n        <- output as String: `LOG: ${message}`\n      default operator ?\n\n    Formatter as abstract\n      format() as abstract\n        -> text as String\n        <- result as String?\n      default operator ?\n\n    UpperFormatter is Formatter\n      override format()\n        -> text as String\n        <- result as String: text.upperCase()\n      default operator ?\n\n    Service as abstract\n      process() as abstract\n        -> input as String\n        <- output as String?\n      default operator ?\n\n    SimpleService is Service\n      logger as Logger!\n      formatter as Formatter!\n\n      override process()\n        -> input as String\n        <- output <- String()\n        formatted <- formatter.format(input)\n        output: logger.log(formatted)\n\n      default operator ?\n\n  defines application\n\n    OrderedApp\n      //Dependencies first, dependents after\n      register ConsoleLogger() as Logger\n      register UpperFormatter() as Formatter\n      register SimpleService() as Service\n\n  defines program\n\n    InjectOrderingDemo() with application of OrderedApp\n      stdout <- Stdout()\n      service as Service!\n      result <- service.process(\"hello world\")\n      stdout.println(result)","migrationContext":"Spring: automatic ordering via @DependsOn or constructor analysis. Guice: Module.configure() ordering matters. EK9: explicit top-to-bottom ordering validated at compile time.","keywords":["DI","before","compile-time","dependency","ordering","register"],"primaryTopics":["registration ordering","DI ordering"],"typicalErrors":[{"error":"E08200","correct":"      register ConsoleLogger() as Logger\n      register UpperFormatter() as Formatter\n      register SimpleService() as Service","incorrect":"      register SimpleService() as Service\n      register ConsoleLogger() as Logger\n      register UpperFormatter() as Formatter","explanation":"Dependencies must be registered BEFORE the components that inject them; registering SimpleService (which injects Logger and Formatter) before them triggers E08200. See ek9 -h E08200 for details."}],"companions":[]}
{"id":1112,"category":"Dependency Injection","question":"Inject a Logger dependency into a component's field using the ! suffix.","url":"https://ek9.io/qa/QA1112.html","alternatePhrasings":["I need a MessageService component that gets its Logger from the DI container","In Spring I'd use @Autowired on a field. Show the EK9 field injection pattern","Given a service component, declare an injection field with the ! suffix","Wire a Logger into a MessageService component through field injection"],"answer":"Declare injection fields with the ! suffix:\n\n  MessageService is Service\n    logger as Logger!\n\nThe compiler validates at compile time that a matching registration exists. Only abstract component types can be injection targets.\n\nSee Q1110 for register/inject basics. See Q227 for compile-time validation.","ek9Example":"defines module qa.di.fieldinjection\n\n  defines component\n\n    Logger as abstract\n      log() as abstract\n        -> message as String\n        <- output as String?\n      default operator ?\n\n    ConsoleLogger is Logger\n      override log()\n        -> message as String\n        <- output as String: `[INFO] ${message}`\n      default operator ?\n\n    Service as abstract\n      send() as abstract\n        -> message as String\n        <- result as String?\n      default operator ?\n\n    //logger field is injected via ! suffix\n    MessageService is Service\n      logger as Logger!\n\n      override send()\n        -> message as String\n        <- result <- String()\n        result: logger.log(message)\n\n      default operator ?\n\n  defines application\n\n    FieldInjectionApp\n      register ConsoleLogger() as Logger\n      register MessageService() as Service\n\n  defines program\n\n    FieldInjectionDemo() with application of FieldInjectionApp\n      stdout <- Stdout()\n      service as Service!\n      result <- service.send(\"Hello from DI\")\n      stdout.println(result)","migrationContext":"Spring: @Autowired private Logger logger. Guice: @Inject Logger logger. EK9: logger as Logger! — the ! suffix marks injection.","keywords":["!","autowired","component","dependency","field","inject"],"primaryTopics":["field injection","! injection suffix"],"typicalErrors":[{"error":"E08150","correct":"      logger as Logger!","incorrect":"      logger as ConsoleLogger!","explanation":"Injection fields must use abstract types. Inject the abstraction (Logger), not the concrete implementation (ConsoleLogger)."}],"companions":[]}
{"id":1113,"category":"Dependency Injection","question":"Define an application that wires three components together.","url":"https://ek9.io/qa/QA1113.html","alternatePhrasings":["I need an application block that registers Logger, Formatter, and Service","In Spring I'd use @Configuration with @Bean methods. Write the EK9 application","Given three components with dependencies, define the application wiring","Set up a complete application registry with multiple registrations"],"answer":"Create the DI registry and connect it to a program:\n\n  defines application\n    MyApp\n      register ConsoleLogger() as Logger\n      register GreetingService() as Service\n\n  defines program\n    Main() with application of MyApp\n\n'with application of' connects the program to the registry. All injection points resolve from this application.\n\nSee Q1110 for register/inject basics. See Q1111 for ordering.","ek9Example":"defines module qa.di.applicationregistry\n\n  defines component\n\n    Logger as abstract\n      log() as abstract\n        -> message as String\n        <- output as String?\n      default operator ?\n\n    ConsoleLogger is Logger\n      override log()\n        -> message as String\n        <- output as String: `[LOG] ${message}`\n      default operator ?\n\n    Formatter as abstract\n      format() as abstract\n        -> text as String\n        <- result as String?\n      default operator ?\n\n    UpperFormatter is Formatter\n      override format()\n        -> text as String\n        <- result as String: text.upperCase()\n      default operator ?\n\n    Service as abstract\n      greet() as abstract\n        -> name as String\n        <- greeting as String?\n      default operator ?\n\n    GreetingService is Service\n      logger as Logger!\n      formatter as Formatter!\n\n      override greet()\n        -> name as String\n        <- greeting <- String()\n        formatted <- formatter.format(name)\n        greeting: logger.log(formatted)\n\n      default operator ?\n\n  defines application\n\n    GreetApp\n      register ConsoleLogger() as Logger\n      register UpperFormatter() as Formatter\n      register GreetingService() as Service\n\n  defines program\n\n    ApplicationRegistryDemo() with application of GreetApp\n      stdout <- Stdout()\n      service as Service!\n      result <- service.greet(\"World\")\n      stdout.println(result)","migrationContext":"Spring: @SpringBootApplication + @Configuration. Guice: AbstractModule.configure(). .NET: Program.cs with builder.Services. EK9: defines application with register statements.","keywords":["DI","application","configuration","register","registry","wire"],"primaryTopics":["defines application","application registry"],"typicalErrors":[{"error":"E08220","correct":"ApplicationRegistryDemo() with application of GreetApp","incorrect":"ApplicationRegistryDemo()","explanation":"A program that uses DI injection must be linked to its application with 'with application of AppName' (EK9 has no wiring annotations). See ek9 -h E08220 for details."}],"companions":[],"oracleToolHint":{"tool":"ek9_scaffold","intent":"application","description":"Oracle can generate an application that wires multiple components together."}}
{"id":1114,"category":"Dependency Injection","question":"Wire an application with all dependencies correctly to pass compile-time DI validation.","url":"https://ek9.io/qa/QA1114.html","alternatePhrasings":["I need to ensure my DI wiring passes the compiler's completeness check","In Spring a missing bean crashes at runtime. Show how EK9 catches it at compile time","Given a service that injects two dependencies, register everything so the compiler is satisfied","Demonstrate that EK9 validates all injection points before the program can run"],"answer":"The compiler performs four DI validations:\n\n  register ConsoleLogger() as Logger\n  register AppService() as Service\n\n1. Completeness -- every ! field has a matching registration. 2. Ordering -- dependencies before dependents. 3. Cycle detection (E08190). 4. Field count limits (E11040). If it compiles, all injection points are guaranteed satisfied.\n\nSee Q227 for DI validation details. See Q229 for circular dependency detection.","ek9Example":"defines module qa.di.compilevalidation\n\n  defines component\n\n    Logger as abstract\n      log() as abstract\n        -> message as String\n        <- output as String?\n      default operator ?\n\n    ConsoleLogger is Logger\n      override log()\n        -> message as String\n        <- output as String: `[OK] ${message}`\n      default operator ?\n\n    Service as abstract\n      run() as abstract\n        <- output as String?\n      default operator ?\n\n    AppService is Service\n      logger as Logger!\n\n      override run()\n        <- output <- String()\n        output: logger.log(\"DI validated at compile time\")\n\n      default operator ?\n\n  defines application\n\n    ValidatedApp\n      //Compiler validates: Logger before AppService, all ! fields satisfied\n      register ConsoleLogger() as Logger\n      register AppService() as Service\n\n  defines program\n\n    DiCompileValidationDemo() with application of ValidatedApp\n      stdout <- Stdout()\n      service as Service!\n      result <- service.run()\n      stdout.println(result)","migrationContext":"Spring: NoSuchBeanDefinitionException at runtime. Guice: CreationException at injector creation. EK9: compile error — the program cannot compile if DI is wrong.","keywords":["DI","compile","completeness","ordering","safety","validate"],"primaryTopics":["compile-time DI validation","DI safety"],"typicalErrors":[{"error":"E08210","correct":"      register ConsoleLogger() as Logger\n      register AppService() as Service","incorrect":"      register AppService() as Service","explanation":"Every injected component (Logger!) must have a matching 'register ... as' entry or the compiler rejects the incomplete wiring. See ek9 -h E08210 for details."}],"companions":[]}
{"id":1115,"category":"Design Patterns and Idioms","question":"Create an aspect that transparently proxies a component's method call.","url":"https://ek9.io/qa/QA1115.html","alternatePhrasings":["I need to wrap a component with an aspect that passes calls through unchanged","In AspectJ I'd define a pointcut. Write the EK9 basic aspect","Given a concrete component, register it with a silent aspect proxy","Set up basic aspect delegation where calls pass through to the real component"],"answer":"Extend Aspect and register with 'with aspect of':\n\n  SilentAspect extends Aspect\n    default operator ?\n  ...\n  register ConcreteComp() as AbstractComp with aspect of SilentAspect()\n\nA silent aspect (no overridden advice) passes all calls through to the real component.\n\nSee Q1116 for aspects with constructor args. See Q1117 for exception handling.","ek9Example":"defines module qa.designpatterns.aspectbasic\n\n  defines component\n\n    Worker as abstract\n      doWork()\n        <- rtn as String: \"AbstractWorker\"\n\n    ConcreteWorker is Worker\n      override doWork()\n        <- rtn as String: \"ConcreteWorker\"\n\n  defines class\n\n    SilentAspect extends Aspect\n      default operator ?\n\n  defines application\n\n    AspectApp\n      register ConcreteWorker() as Worker with aspect of SilentAspect()\n\n  defines program\n\n    AspectBasicDemo() with application of AspectApp\n      stdout <- Stdout()\n      worker as Worker!\n      result <- worker.doWork()\n      stdout.println(result)","migrationContext":"AspectJ: @Aspect + @Around. Spring AOP: @Aspect + @Before/@After. Python: decorators. EK9: extends Aspect + 'with aspect of' in registration.","keywords":["AOP","aspect","delegation","proxy","transparent","wrap"],"primaryTopics":["basic aspect","aspect delegation"],"typicalErrors":[{"error":"E50200","correct":"      register ConcreteWorker() as Worker with aspect of SilentAspect()","incorrect":"      @Aspect register ConcreteWorker() as Worker with aspect of SilentAspect()","explanation":"EK9 has no '@'-annotations — '@Aspect' is parsed as an invalid directive; apply an aspect with 'with aspect of AspectClass()' in the register statement. See ek9 -h E50200 for details."}],"companions":[],"oracleToolHint":{"tool":"ek9_scaffold","intent":"aspect","description":"Oracle can generate an aspect that proxies a component method call."}}
{"id":1116,"category":"Design Patterns and Idioms","question":"Create an aspect that takes a label in its constructor for identification.","url":"https://ek9.io/qa/QA1116.html","alternatePhrasings":["I need an aspect with configurable state passed through its constructor","In Spring AOP I'd use @Aspect with fields. Write the EK9 aspect with constructor args","Given an aspect that needs a name, pass it via the constructor at registration","Build a parameterised aspect that carries configuration"],"answer":"Aspects are classes with constructors and fields:\n\n  LabelledAspect extends Aspect\n    label <- String()\n    LabelledAspect()\n      -> label as String\n      this.label :=: label\n  ...\n  register Impl() as Abstract with aspect of LabelledAspect(\"audit\")\n\nPass constructor arguments at registration time.\n\nSee Q1115 for basic aspect. See Q1117 for exception handling.","ek9Example":"defines module qa.designpatterns.aspectconstructor\n\n  defines component\n\n    Printer as abstract\n      print()\n        <- rtn as String: \"AbstractPrinter\"\n\n    ConcretePrinter is Printer\n      override print()\n        <- rtn as String: \"ConcretePrinter\"\n\n  defines class\n\n    LabelledAspect extends Aspect\n      label <- String()\n\n      LabelledAspect()\n        -> label as String\n        this.label :=: label\n\n      default operator ?\n\n  defines application\n\n    AspectConstructorApp\n      register ConcretePrinter() as Printer with aspect of LabelledAspect(\"audit\")\n\n  defines program\n\n    AspectConstructorDemo() with application of AspectConstructorApp\n      stdout <- Stdout()\n      printer as Printer!\n      result <- printer.print()\n      stdout.println(result)","migrationContext":"Spring AOP: @Aspect class with @Value injection. AspectJ: aspect with constructor. EK9: Aspect subclass with constructor, configured at registration.","keywords":["AOP","aspect","configuration","constructor","parameters"],"primaryTopics":["aspect with constructor","parameterised aspect"],"typicalErrors":[],"companions":[]}
{"id":1117,"category":"Design Patterns and Idioms","question":"Handle an exception that occurs inside an aspect-proxied component call.","url":"https://ek9.io/qa/QA1117.html","alternatePhrasings":["I need to understand what happens when a proxied method throws through an aspect","In Spring AOP exceptions propagate through advice. Show the EK9 aspect exception path","Given a component that throws, verify the exception passes through the aspect to the caller","Test exception propagation through an aspect proxy"],"answer":"Exceptions propagate through aspects unchanged:\n\n  calc as Calculator!\n  result <- calc.divide(10)\n\nA silent aspect does not catch or modify exceptions -- they pass straight through. The caller's try/catch handles exceptions as if no aspect were present.\n\nSee Q1115 for basic aspect. See Q1116 for aspect with constructor.","ek9Example":"defines module qa.designpatterns.aspectexception\n\n  defines component\n\n    Calculator as abstract\n      divide() as abstract\n        -> divisor as Integer\n        <- result as Integer?\n\n    SafeCalculator is Calculator\n      override divide()\n        -> divisor as Integer\n        <- result as Integer: 100 / divisor\n\n  defines class\n\n    AuditAspect extends Aspect\n      default operator ?\n\n  defines application\n\n    ExceptionApp\n      register SafeCalculator() as Calculator with aspect of AuditAspect()\n\n  defines program\n\n    AspectExceptionDemo() with application of ExceptionApp\n      stdout <- Stdout()\n      calc as Calculator!\n\n      //Normal call through aspect\n      result <- calc.divide(10)\n      stdout.println(`100 / 10 = ${result}`)","migrationContext":"Spring AOP: exceptions propagate through @Around advice unless caught. AspectJ: same propagation. EK9: same — exceptions pass through the aspect proxy transparently.","keywords":["aspect","error","exception","propagate","proxy","try catch"],"primaryTopics":["aspect exception handling","exception propagation"],"typicalErrors":[],"companions":[]}
{"id":1118,"category":"Design Patterns and Idioms","question":"Apply an aspect to a component that has multiple methods.","url":"https://ek9.io/qa/QA1118.html","alternatePhrasings":["I have a component with start, stop, and status methods — apply an aspect to all of them","In AspectJ a pointcut can match multiple methods. Show the EK9 multi-method aspect","Given a service with several methods, wrap all of them with a single aspect","Verify that an aspect proxies every method on a component, not just one"],"answer":"An aspect proxies ALL component methods:\n\n  service.start()\n  service.status()\n  service.stop()\n\nEvery method call goes through the aspect proxy. A silent aspect forwards all calls unchanged.\n\nSee Q1115 for basic aspect. See Q1119 for aspect ordering.","ek9Example":"defines module qa.designpatterns.aspectmultiplemethods\n\n  defines component\n\n    Service as abstract\n      start()\n        <- rtn as String: \"abstract-start\"\n\n      stop()\n        <- rtn as String: \"abstract-stop\"\n\n      status()\n        <- rtn as String: \"abstract-status\"\n\n    ServiceImpl is Service\n      override start()\n        <- rtn as String: \"started\"\n\n      override stop()\n        <- rtn as String: \"stopped\"\n\n      override status()\n        <- rtn as String: \"running\"\n\n  defines class\n\n    SilentAspect extends Aspect\n      default operator ?\n\n  defines application\n\n    MultiMethodApp\n      register ServiceImpl() as Service with aspect of SilentAspect()\n\n  defines program\n\n    AspectMultipleMethodsDemo() with application of MultiMethodApp\n      stdout <- Stdout()\n      service as Service!\n\n      //All three methods go through the aspect\n      stdout.println(service.start())\n      stdout.println(service.status())\n      stdout.println(service.stop())","migrationContext":"Spring AOP: pointcut expressions select methods. AspectJ: wildcards in pointcuts. EK9: aspects apply to ALL methods on the registered component automatically.","keywords":["all","aspect","methods","multiple","proxy","wrap"],"primaryTopics":["multi-method aspect","aspect scope"],"typicalErrors":[],"companions":[]}
{"id":1119,"category":"Design Patterns and Idioms","question":"Register a component with an aspect and verify the aspect proxy is transparent.","url":"https://ek9.io/qa/QA1119.html","alternatePhrasings":["I want to confirm that an aspect does not change the return value of a method","Show that a silent aspect passes method results through unchanged","Given two registrations — one with aspect, one without — verify identical results","Demonstrate aspect transparency by comparing proxied and direct calls"],"answer":"A silent aspect is transparent -- calls and returns pass through unchanged:\n\n  greeter as Greeter!\n  result <- greeter.greet(\"World\")\n\nThe caller cannot tell whether an aspect is present. This is the foundation of AOP in EK9.\n\nSee Q1115 for basic aspect. See Q1120 for no-aspect baseline.","ek9Example":"defines module qa.designpatterns.aspectordering\n\n  defines component\n\n    Greeter as abstract\n      greet() as abstract\n        -> name as String\n        <- rtn as String?\n\n    SimpleGreeter is Greeter\n      override greet()\n        -> name as String\n        <- rtn as String: `Hello ${name}`\n\n  defines class\n\n    TransparentAspect extends Aspect\n      default operator ?\n\n  defines application\n\n    AspectOrderApp\n      register SimpleGreeter() as Greeter with aspect of TransparentAspect()\n\n  defines program\n\n    AspectOrderingDemo() with application of AspectOrderApp\n      stdout <- Stdout()\n      greeter as Greeter!\n      result <- greeter.greet(\"World\")\n      stdout.println(result)","migrationContext":"Spring AOP: @Around that calls proceed() is transparent. AspectJ: proceed() in around advice. EK9: silent Aspect subclass — transparent by default.","keywords":["aspect","ordering","proxy","transparent","unchanged"],"primaryTopics":["aspect transparency","aspect ordering"],"typicalErrors":[{"error":"E50200","correct":"    TransparentAspect extends Aspect\n      default operator ?","incorrect":"    TransparentAspect extends Aspect\n      @Around proceed()","explanation":"EK9 has no Java/Spring-style annotations; the '@' prefix is reserved for compiler directives and '@Around' is not a valid one. See ek9 -h E50200 for details."}],"companions":[]}
{"id":1120,"category":"Design Patterns and Idioms","question":"Register a component without an aspect to show the baseline behaviour.","url":"https://ek9.io/qa/QA1120.html","alternatePhrasings":["I want to see how a component behaves without any aspect wrapping","Show the difference between registering with and without an aspect","Given a service, register it directly without 'with aspect of'","Demonstrate a plain component registration for comparison with aspect examples"],"answer":"Register without 'with aspect of' for direct access:\n\n  register DirectWorker() as Worker\n\nNo proxy, no interception -- method calls go straight to the implementation. Use aspects only when you need cross-cutting behaviour.\n\nSee Q1115 for aspect basics. See Q1110 for register/inject.","ek9Example":"defines module qa.designpatterns.noaspect\n\n  defines component\n\n    Worker as abstract\n      doWork()\n        <- rtn as String: \"abstract\"\n\n    DirectWorker is Worker\n      override doWork()\n        <- rtn as String: \"direct result\"\n\n  defines application\n\n    NoAspectApp\n      //No aspect — direct access\n      register DirectWorker() as Worker\n\n  defines program\n\n    NoAspectDemo() with application of NoAspectApp\n      stdout <- Stdout()\n      worker as Worker!\n      result <- worker.doWork()\n      stdout.println(result)","migrationContext":"Spring: @Service without @Aspect is the default. EK9: register without 'with aspect of' is the default.","keywords":["baseline","direct","no aspect","plain","registration"],"primaryTopics":["plain registration","no aspect baseline"],"typicalErrors":[{"error":"E50001","correct":"register DirectWorker() as Worker","incorrect":"register DirectWorker() as Worker with aspect of NoAspect()","explanation":"No need to invent a placeholder aspect — 'with aspect of NoAspect()' references an undefined aspect that fails to resolve; simply omit 'with aspect of' for direct registration. See ek9 -h E50001 for details."}],"companions":[]}
{"id":1121,"category":"Design Patterns and Idioms","question":"Write a dispatcher over a sealed Shape hierarchy with Circle, Square, and Triangle.","url":"https://ek9.io/qa/QA1121.html","alternatePhrasings":["I need to handle different shape types without instanceof or casting","In Java I'd use instanceof checks or visitor pattern. Write the EK9 dispatcher","Given a sealed trait with three permitted types, dispatch on each type at runtime","Replace a chain of type checks with EK9's dispatcher mechanism"],"answer":"Define sealed trait, then dispatcher method with overloads:\n\n  describe() as dispatcher\n    -> shape as Shape\n    <- rtn as String: \"Unknown\"\n\n  describe()\n    -> shape as Circle\n    <- rtn as String: \"Circle\"\n\nMark the base method 'as dispatcher'. The compiler generates dispatch logic -- no instanceof, no casting.\n\nSee Q1122 for dispatcher with fallback. See Q1123 for deep hierarchy.","ek9Example":"defines module qa.designpatterns.dispatchersealed\n\n  defines trait\n\n    Shape allow only Circle, Square, Triangle\n      name() as abstract\n        <- rtn as String?\n\n  defines class\n\n    Circle with trait of Shape\n      override name()\n        <- rtn as String: \"Circle\"\n\n    Square with trait of Shape\n      override name()\n        <- rtn as String: \"Square\"\n\n    Triangle with trait of Shape\n      override name()\n        <- rtn as String: \"Triangle\"\n\n    ShapeProcessor\n\n      describe() as dispatcher\n        -> shape as Shape\n        <- rtn as String: \"Unknown\"\n\n      describe()\n        -> shape as Circle\n        <- rtn as String: \"Handled Circle\"\n\n      describe()\n        -> shape as Square\n        <- rtn as String: \"Handled Square\"\n\n      describe()\n        -> shape as Triangle\n        <- rtn as String: \"Handled Triangle\"\n\n  defines program\n\n    DispatcherSealedDemo()\n      stdout <- Stdout()\n      processor <- ShapeProcessor()\n\n      stdout.println(processor.describe(Circle()))\n      stdout.println(processor.describe(Square()))\n      stdout.println(processor.describe(Triangle()))","migrationContext":"Java: instanceof + cast chain or visitor pattern. Kotlin: sealed class + when. Rust: enum + match. EK9: sealed trait + dispatcher — compiler generates dispatch, no casting.","keywords":["dispatcher","pattern matching","sealed","switch","trait","type"],"primaryTopics":["dispatcher pattern","sealed trait dispatch"],"typicalErrors":[{"error":"E01010","correct":"      describe() as dispatcher\n        -> shape as Shape","incorrect":"      if shape instanceof Circle","explanation":"EK9 has no instanceof. Use 'as dispatcher' to dispatch on runtime types."}],"companions":[],"oracleToolHint":{"tool":"ek9_scaffold","intent":"dispatcher","description":"Oracle can generate a dispatcher over a sealed type hierarchy with handler methods."}}
{"id":1122,"category":"Design Patterns and Idioms","question":"Write a dispatcher with a fallback handler for unmatched types.","url":"https://ek9.io/qa/QA1122.html","alternatePhrasings":["I need a dispatcher that handles known types and has a default for everything else","In Java I'd use a default case in a switch. Write the EK9 dispatcher default handler","Given an open hierarchy, dispatch known types and fall back for unknown ones","Handle specific types with dedicated handlers and catch-all for the rest"],"answer":"The base dispatcher method IS the fallback:\n\n  describe() as dispatcher\n    -> shape as Shape\n    <- rtn as String: \"Unknown shape\"\n\nIf no specific overload matches the runtime type, the base method runs. For sealed traits with exhaustive handlers, the fallback is never reached.\n\nSee Q1121 for sealed trait dispatch. See Q1123 for deep hierarchy.","ek9Example":"defines module qa.designpatterns.dispatcherfallback\n\n  defines class\n\n    Shape as open\n      name()\n        <- rtn as String: \"Shape\"\n\n    Circle extends Shape\n      override name()\n        <- rtn as String: \"Circle\"\n\n    Square extends Shape\n      override name()\n        <- rtn as String: \"Square\"\n\n    //No specific handler for this type — falls through to base\n    Hexagon extends Shape\n      override name()\n        <- rtn as String: \"Hexagon\"\n\n    ShapeDescriber\n\n      describe() as dispatcher\n        -> shape as Shape\n        <- rtn as String: \"Unknown shape\"\n\n      describe()\n        -> shape as Circle\n        <- rtn as String: \"It is a Circle\"\n\n      describe()\n        -> shape as Square\n        <- rtn as String: \"It is a Square\"\n\n  defines program\n\n    DispatcherFallbackDemo()\n      stdout <- Stdout()\n      describer <- ShapeDescriber()\n\n      //Matched handlers\n      stdout.println(describer.describe(Circle()))\n      stdout.println(describer.describe(Square()))\n\n      //Fallback — no specific handler for Hexagon\n      stdout.println(describer.describe(Hexagon()))","migrationContext":"Java: switch default case or else branch. Kotlin: sealed when + else. EK9: the dispatcher base method is the default — no separate 'default' keyword needed.","keywords":["catch-all","default","dispatcher","fallback","unmatched"],"primaryTopics":["dispatcher fallback","default handler"],"typicalErrors":[{"error":"E01010","correct":"      describe() as dispatcher\n        -> shape as Shape\n        <- rtn as String: \"Unknown shape\"","incorrect":"      describe() as dispatcher\n        -> shape as Shape\n        <- rtn as String?\n        default:\n          rtn: \"Unknown\"","explanation":"The dispatcher base method IS the fallback. No 'default:' keyword — just provide the default return value."}],"companions":[]}
{"id":1123,"category":"Design Patterns and Idioms","question":"Dispatch on a deep class hierarchy where exact match wins over parent handler.","url":"https://ek9.io/qa/QA1123.html","alternatePhrasings":["I have Shape -> Polygon -> Quadrilateral -> Square and need the most specific handler","In Java I'd check instanceof in order. Show how EK9 dispatcher picks the best match","Given a deep hierarchy, verify that a Square handler wins over a Polygon handler","Set up dispatcher resolution across multiple inheritance levels"],"answer":"Most specific handler wins via cost-based selection:\n\n  render() as dispatcher -> shape as Shape\n  render() -> shape as Polygon\n  render() -> shape as Square\n\nPassing Quadrilateral selects Polygon handler (closest match). Passing Square selects Square handler (exact), not Polygon.\n\nSee Q1121 for sealed dispatch. See Q1122 for fallback handling.","ek9Example":"defines module qa.designpatterns.dispatcherdeep\n\n  defines class\n\n    Shape as abstract\n      default operator ?\n\n    Polygon is Shape as open\n      default operator ?\n\n    Quadrilateral is Polygon as open\n      default operator ?\n\n    Square is Quadrilateral\n      default operator ?\n\n    GeometryRenderer\n\n      render() as dispatcher\n        -> shape as Shape\n        <- rtn as String: \"Shape handler\"\n\n      render()\n        -> shape as Polygon\n        <- rtn as String: \"Polygon handler\"\n\n      render()\n        -> shape as Square\n        <- rtn as String: \"Square handler\"\n\n  defines program\n\n    DispatcherDeepDemo()\n      stdout <- Stdout()\n      renderer <- GeometryRenderer()\n\n      //Polygon -> Polygon handler\n      stdout.println(renderer.render(Polygon()))\n\n      //Quadrilateral -> Polygon handler (closest match)\n      stdout.println(renderer.render(Quadrilateral()))\n\n      //Square -> Square handler (exact match beats Polygon)\n      stdout.println(renderer.render(Square()))","migrationContext":"Java: ordered instanceof checks (fragile). C#: pattern matching with type guards. EK9: dispatcher with automatic cost-based resolution — most specific handler wins.","keywords":["cost-based","deep","dispatcher","hierarchy","resolution","specific"],"primaryTopics":["deep hierarchy dispatch","cost-based resolution"],"typicalErrors":[{"error":"E01010","correct":"      render() as dispatcher\n        -> shape as Shape","incorrect":"      if shape instanceof Square","explanation":"EK9 has no instanceof. The dispatcher automatically selects the most specific handler."}],"companions":[]}
{"id":1124,"category":"Design Patterns and Idioms","question":"Dispatch on two parameters to handle different shape combinations.","url":"https://ek9.io/qa/QA1124.html","alternatePhrasings":["I need to handle Circle+Circle differently from Circle+Rectangle interactions","In Java I'd use the visitor pattern for double dispatch. Write the EK9 two-param dispatcher","Given two Shape parameters, select the correct handler based on both runtime types","Set up a dispatcher that dispatches on two parameter types simultaneously"],"answer":"Multi-parameter dispatch -- parameter order matters:\n\n  combine() as dispatcher\n    -> s1 as Shape, s2 as Shape\n    <- rtn as String: \"Generic\"\n\n  combine() -> s1 as Circle, s2 as Circle\n    <- rtn as String: \"Circle-Circle\"\n\nCircle+Rectangle has a handler but Rectangle+Circle falls through to generic.\n\nSee Q1121 for single-param dispatch. See Q1123 for deep hierarchy.","ek9Example":"defines module qa.designpatterns.dispatchertwoparam\n\n  defines class\n\n    Shape as abstract\n      default operator ?\n\n    Circle is Shape\n      default operator ?\n\n    Rectangle is Shape\n      default operator ?\n\n    Combiner\n\n      combine() as dispatcher\n        ->\n          s1 as Shape\n          s2 as Shape\n        <- rtn as String: \"Generic\"\n\n      combine()\n        ->\n          s1 as Circle\n          s2 as Circle\n        <- rtn as String: \"Circle-Circle\"\n\n      combine()\n        ->\n          s1 as Circle\n          s2 as Rectangle\n        <- rtn as String: \"Circle-Rectangle\"\n\n  defines program\n\n    DispatcherTwoParamDemo()\n      stdout <- Stdout()\n      combiner <- Combiner()\n\n      circle <- Circle()\n      rectangle <- Rectangle()\n\n      stdout.println(combiner.combine(circle, circle))\n      stdout.println(combiner.combine(circle, rectangle))\n      //Rectangle+Circle has no specific handler — falls to Generic\n      stdout.println(combiner.combine(rectangle, circle))","migrationContext":"Java: double dispatch via visitor pattern (complex). Kotlin: no built-in. EK9: two-param dispatcher — direct multi-dispatch without visitor boilerplate.","keywords":["dispatcher","double dispatch","multi-dispatch","two parameter","visitor"],"primaryTopics":["two-parameter dispatcher","multi-dispatch"],"typicalErrors":[{"error":"E01010","correct":"      combine() as dispatcher\n        ->\n          s1 as Shape","incorrect":"      combine(Shape s1, Shape s2)","explanation":"EK9 uses separate parameter lines. Mark the base method 'as dispatcher' with both parameters."}],"companions":[]}
{"id":1125,"category":"Advanced Type System","question":"Create a dynamic function that captures a prefix variable and prepends it to input.","url":"https://ek9.io/qa/QA1125.html","alternatePhrasings":["I need a closure that captures a local variable and uses it in its body","In JavaScript I'd create a closure over a variable. Write the EK9 dynamic function","Given a prefix string, create a dynamic function that adds it to any input","Build a function that captures state with named capture syntax"],"answer":"Capture a variable into a dynamic function:\n\n  transform <- (prefix: prefixText) extends Transformer as function\n    output: `${prefix} ${input}`\n\n(prefix: prefixText) captures local 'prefixText' into a field named 'prefix'. The abstract function type defines parameter and return types.\n\nSee Q1126 for dynamic classes.","ek9Example":"defines module qa.advancedtypes.dynamicfunctioncapture\n\n  defines function\n\n    Transformer as abstract\n      -> input as String\n      <- output as String?\n\n  defines program\n\n    DynamicFunctionCaptureDemo()\n      stdout <- Stdout()\n      prefixText <- \"Hey\"\n\n      //Create dynamic function capturing prefixText as 'prefix'\n      transform <- (prefix: prefixText) extends Transformer as function\n        output: `${prefix} ${input}`\n\n      result <- transform(\"World\")\n      stdout.println(result)","migrationContext":"JavaScript: const fn = (input) => prefix + input (closure). Java: lambda with effectively final variable. Python: lambda or closure. EK9: named capture in dynamic function.","keywords":["capture","closure","dynamic","function","lambda","state"],"primaryTopics":["dynamic function","named capture"],"typicalErrors":[{"error":"E01010","correct":"      transform <- (prefix: prefixText) extends Transformer as function","incorrect":"      transform <- (input) -> prefix + input","explanation":"EK9 dynamic functions extend an abstract function type. Use 'extends TypeName as function' with a capture list."}],"companions":[]}
{"id":1126,"category":"Advanced Type System","question":"Create a dynamic class that implements a Describable trait inline.","url":"https://ek9.io/qa/QA1126.html","alternatePhrasings":["I need an anonymous class implementing a trait, created inline with captured state","In Java I'd use an anonymous inner class. Write the EK9 dynamic class equivalent","Given a Describable trait, create an inline implementation that captures a label variable","Build a one-off trait implementation without defining a named class"],"answer":"Create a dynamic class implementing a trait inline:\n\n  myObj <- (label) with trait of Describable as class\n    override describe() as pure\n      <- rtn as String: label\n    default operator ?\n\n(label) captures the local variable. The dynamic class implements the trait's abstract methods -- no named class needed.\n\nSee Q1125 for dynamic functions. See Q1129 for trait delegation.","ek9Example":"defines module qa.advancedtypes.dynamicclasstrait\n\n  defines trait\n\n    Describable\n      describe() as pure abstract\n        <- rtn as String?\n\n  defines program\n\n    DynamicClassTraitDemo()\n      stdout <- Stdout()\n      label <- \"Dynamic\"\n\n      //Create inline implementation of Describable\n      myDescribable <- (label) with trait of Describable as class\n        override describe() as pure\n          <- rtn as String: label\n        default operator ?\n\n      stdout.println(myDescribable.describe())","migrationContext":"Java: new Describable() { @Override String describe() { return label; } }. Kotlin: object : Describable { }. EK9: (captures) with trait of X as class.","keywords":["anonymous","capture","class","dynamic","inline","trait"],"primaryTopics":["dynamic class","inline trait implementation"],"typicalErrors":[{"error":"E01075","correct":"      myDescribable <- (label) with trait of Describable as class\n        override describe() as pure\n          <- rtn as String: label\n        default operator ?","incorrect":"      myDescribable <- new Describable()","explanation":"EK9 has no 'new' keyword - create an inline implementation with '(captures) with trait of TraitName as class'. See ek9 -h E01075 for details."}],"companions":[]}
{"id":1127,"category":"Advanced Type System","question":"Create a dynamic class with multiple methods implementing a trait inline.","url":"https://ek9.io/qa/QA1127.html","alternatePhrasings":["I need an inline class that implements two abstract methods from a trait","In Java I'd use an anonymous class with multiple method overrides. Write the EK9 way","Given a trait with greet() and farewell(), create a one-off inline implementation","Build a dynamic class that overrides multiple trait methods"],"answer":"Override multiple abstract methods in a dynamic class:\n\n  obj <- () with trait of Speaker as class\n    override greet() as pure\n      <- rtn as String: \"Hello\"\n    override farewell() as pure\n      <- rtn as String: \"Goodbye\"\n    default operator ?\n\nEvery abstract method in the trait must be overridden in the dynamic class body.\n\nSee Q1125 for dynamic functions. See Q1126 for dynamic class with capture.","ek9Example":"defines module qa.advancedtypes.dynamicclassmethods\n\n  defines trait\n\n    Speaker\n      greet() as pure abstract\n        <- rtn as String?\n      farewell() as pure abstract\n        <- rtn as String?\n\n  defines program\n\n    DynamicClassMethodsDemo()\n      stdout <- Stdout()\n\n      speaker <- () with trait of Speaker as class\n        override greet() as pure\n          <- rtn as String: \"Hello\"\n        override farewell() as pure\n          <- rtn as String: \"Goodbye\"\n        default operator ?\n\n      stdout.println(speaker.greet())\n      stdout.println(speaker.farewell())","migrationContext":"Java: anonymous inner class with multiple method overrides. Kotlin: object expression. Python: lambda (single method only). EK9: dynamic class implementing full trait.","keywords":["class","dynamic","inline","methods","multiple","trait"],"primaryTopics":["multi-method dynamic class","inline implementation"],"typicalErrors":[{"error":"E01075","correct":"      stdout <- Stdout()","incorrect":"      stdout <- new Stdout()","explanation":"EK9 has no 'new' keyword — instantiate a type by calling its constructor directly, e.g. Stdout(). See ek9 -h E01075 for details."}],"companions":[]}
{"id":1128,"category":"Advanced Type System","question":"Pass a dynamic class as a function argument.","url":"https://ek9.io/qa/QA1128.html","alternatePhrasings":["I need to create an inline trait implementation and pass it to a function","In Java I'd pass an anonymous class to a method. Write the EK9 equivalent","Given a function that takes a Describable, create and pass a dynamic implementation","Build a dynamic class inline at the call site and pass it directly"],"answer":"Pass a dynamic class to a function as the trait type:\n\n  myObj <- (label) with trait of Describable as class\n    override describe() as pure <- rtn as String: label\n    default operator ?\n  printDescription(myObj)\n\nThe function receives it as Describable -- it does not know or care that it is a dynamic class.\n\nSee Q1126 for dynamic class basics. See Q1125 for dynamic functions.","ek9Example":"defines module qa.advancedtypes.dynamicclassasargument\n\n  defines trait\n\n    Describable\n      describe() as pure abstract\n        <- rtn as String?\n\n  defines function\n\n    printDescription()\n      -> item as Describable\n      <- output as String: item.describe()\n\n  defines program\n\n    DynamicClassAsArgumentDemo()\n      stdout <- Stdout()\n      label <- \"Widget\"\n\n      //Create dynamic class implementing Describable\n      widget <- (label) with trait of Describable as class\n        override describe() as pure\n          <- rtn as String: label\n        default operator ?\n\n      //Pass it to a function that takes the trait type\n      result <- printDescription(widget)\n      stdout.println(result)","migrationContext":"Java: printDescription(new Describable() { ... }). Kotlin: printDescription(object : Describable { ... }). EK9: create dynamic class, pass as trait type.","keywords":["argument","class","dynamic","function","inline","pass"],"primaryTopics":["dynamic class as argument","passing inline implementations"],"typicalErrors":[{"error":"E01075","correct":"printDescription(widget)","incorrect":"printDescription(new Describable())","explanation":"EK9 has no 'new' keyword; instantiate a type directly as TypeName() (or build a dynamic class first and pass that). See ek9 -h E01075 for details."}],"companions":[]}
{"id":1129,"category":"Sealed Types and Traits","question":"Delegate trait methods to another object using the 'by' keyword.","url":"https://ek9.io/qa/QA1129.html","alternatePhrasings":["I need a class that forwards all trait method calls to a delegate field","In Kotlin I'd use 'by' delegation. Write the EK9 trait delegation equivalent","Given a Processor trait, create a delegating wrapper that forwards to a real processor","Implement the delegation pattern using 'with trait of X by field'"],"answer":"Use 'by fieldName' to delegate all trait methods:\n\n  DelegatingProcessor with trait of Processor by delegate\n    delegate as Processor?\n\nAll trait methods are forwarded to the delegate field. Composition and delegation replace deep class hierarchies.\n\nSee Q1130 for chained delegation. See Q1131 for diamond resolution.","ek9Example":"defines module qa.sealedtraits.traitdelegationbasic\n\n  defines trait\n\n    Processor\n      process() as pure\n        <- result as String: \"default\"\n\n  defines class\n\n    SimpleProcessor with trait of Processor\n\n      override process() as pure\n        <- result as String: \"simple\"\n\n      default operator ?\n\n    DelegatingProcessor with trait of Processor by delegate\n      delegate as Processor?\n\n      default private DelegatingProcessor()\n\n      DelegatingProcessor()\n        -> proc as Processor\n        require proc?\n        delegate := proc\n\n      default operator ?\n\n  defines program\n\n    TraitDelegationBasicDemo()\n      stdout <- Stdout()\n\n      simple <- SimpleProcessor()\n      delegating <- DelegatingProcessor(simple)\n\n      //Call goes through delegating -> delegate -> simple\n      result <- delegating.process()\n      stdout.println(`Result: ${result}`)","migrationContext":"Kotlin: class Wrapper(p: Processor): Processor by p. Java: manual forwarding methods. Go: embedding. EK9: 'with trait of X by field' — compiler generates forwarding.","keywords":["by","composition","delegation","forward","proxy","trait"],"primaryTopics":["trait delegation","by keyword"],"typicalErrors":[{"error":"E50020","correct":"    SimpleProcessor with trait of Processor","incorrect":"    SimpleProcessor is Processor","explanation":"A trait is adopted with 'with trait of', not 'is' (which extends a class); using 'is' on a trait is an incompatible-genus error. See ek9 -h E50020 for details."}],"companions":[],"oracleToolHint":{"tool":"ek9_implement","intent":"trait","description":"Oracle can generate trait delegation using the 'by' keyword with correct method forwarding."}}
{"id":1130,"category":"Sealed Types and Traits","question":"Chain trait delegations so Top delegates to Middle which delegates to Final.","url":"https://ek9.io/qa/QA1130.html","alternatePhrasings":["I need a multi-level delegation chain where each layer forwards to the next","In Java I'd chain decorator objects. Write the EK9 chained trait delegation","Given three classes implementing a trait, wire them so calls flow Top -> Middle -> Final","Build a delegation pipeline across multiple levels using 'by' keyword"],"answer":"Chain delegators -- each level forwards to the next:\n\n  TopProducer with trait of Producer by delegate\n    delegate as Producer?\n  MiddleProducer with trait of Producer by delegate\n    delegate as Producer?\n  FinalProducer with trait of Producer\n    override produce() as pure <- result as String: \"final\"\n\nCalling top.produce() flows: Top -> Middle -> Final.\n\nSee Q1129 for basic delegation. See Q1131 for diamond resolution.","ek9Example":"defines module qa.sealedtraits.traitdelegationchain\n\n  defines trait\n\n    Producer\n      produce() as pure\n        <- result as String: \"default\"\n\n  defines class\n\n    FinalProducer with trait of Producer\n      override produce() as pure\n        <- result as String: \"final\"\n      default operator ?\n\n    MiddleProducer with trait of Producer by delegate\n      delegate as Producer?\n\n      default private MiddleProducer()\n\n      MiddleProducer()\n        -> prod as Producer\n        require prod?\n        delegate := prod\n\n      default operator ?\n\n    TopProducer with trait of Producer by delegate\n      delegate as Producer?\n\n      default private TopProducer()\n\n      TopProducer()\n        -> prod as Producer\n        require prod?\n        delegate := prod\n\n      default operator ?\n\n  defines program\n\n    TraitDelegationChainDemo()\n      stdout <- Stdout()\n\n      last <- FinalProducer()\n      middle <- MiddleProducer(last)\n      top <- TopProducer(middle)\n\n      //Call flows: Top -> Middle -> Final\n      result <- top.produce()\n      stdout.println(`Result: ${result}`)","migrationContext":"Java: Decorator pattern with chained wrappers. Python: decorator chain. Go: nested embedding. EK9: chained 'by delegate' fields.","keywords":["chain","delegation","forward","multi-level","pipeline","trait"],"primaryTopics":["chained delegation","multi-level forwarding"],"typicalErrors":[{"error":"E05030","correct":"    TopProducer with trait of Producer by delegate\n      delegate as Producer?","incorrect":"    TopProducer extends MiddleProducer","explanation":"Use delegation (by keyword) instead of inheritance for chain-of-responsibility patterns."}],"companions":[]}
{"id":1131,"category":"Sealed Types and Traits","question":"Resolve diamond inheritance when a class adopts two traits that share a parent.","url":"https://ek9.io/qa/QA1131.html","alternatePhrasings":["I have LeftTrait and RightTrait both extending TopTrait — resolve the conflict","In Java interfaces with diamond inheritance need default method resolution. Show the EK9 way","Given a diamond pattern with conflicting methods, override to resolve them","Handle the case where two traits provide conflicting implementations of the same method"],"answer":"Resolve diamond conflicts with explicit override:\n\n  DiamondClass with trait of LeftTrait, RightTrait\n    override getShared() as pure\n      <- rtn as String: \"diamond-shared\"\n\nWhen two traits extend the same parent, conflicting methods must be overridden. Use TraitName.methodName() to call a specific trait's version.\n\nSee Q1129 for basic delegation. See Q1132 for explicit trait calls.","ek9Example":"defines module qa.sealedtraits.traitdiamond\n\n  defines trait\n\n    TopTrait\n      getShared() as pure\n        <- mainValue as String: \"top-shared\"\n\n    LeftTrait is TopTrait\n      override getShared() as pure\n        <- mainValue as String: \"left-shared\"\n\n    RightTrait is TopTrait\n      override getShared() as pure\n        <- mainValue as String: \"right-shared\"\n\n  defines class\n\n    DiamondClass with trait of LeftTrait, RightTrait\n\n      //Must override to resolve the conflict\n      override getShared() as pure\n        <- mainValue as String: \"diamond-resolved\"\n\n      //Explicit access to each trait's version\n      getLeftVersion() as pure\n        <- mainValue as String: LeftTrait.getShared()\n\n      getRightVersion() as pure\n        <- mainValue as String: RightTrait.getShared()\n\n      default operator ?\n\n  defines program\n\n    TraitDiamondDemo()\n      stdout <- Stdout()\n\n      diamond <- DiamondClass()\n      stdout.println(`Resolved: ${diamond.getShared()}`)\n      stdout.println(`Left: ${diamond.getLeftVersion()}`)\n      stdout.println(`Right: ${diamond.getRightVersion()}`)","migrationContext":"Java: interface default method conflict requires explicit override. C#: explicit interface implementation. EK9: override conflicting methods + TraitName.method() for explicit access.","keywords":["conflict","diamond","multiple","override","resolve","trait"],"primaryTopics":["diamond inheritance","trait conflict resolution"],"typicalErrors":[{"error":"E05120","correct":"      override getShared() as pure\n        <- mainValue as String: \"left-shared\"","incorrect":"      getShared() as pure\n        <- rtn as String: \"resolved\"","explanation":"Conflicting trait methods require the 'override' keyword to resolve the ambiguity."}],"companions":[]}
{"id":1132,"category":"Sealed Types and Traits","question":"Call a specific trait's method implementation using TraitName.method() syntax.","url":"https://ek9.io/qa/QA1132.html","alternatePhrasings":["I need to access a particular trait's version of a method in a class with multiple traits","In Java I'd use InterfaceName.super.method(). Write the EK9 explicit trait call","Given a class with two traits providing getInfo(), call each trait's version explicitly","Disambiguate trait method calls using the TraitName.methodName() syntax"],"answer":"Call a specific trait's implementation by name:\n\n  getLeftInfo() as pure\n    <- rtn as String: LeftInfo.getInfo()\n  getRightInfo() as pure\n    <- rtn as String: RightInfo.getInfo()\n\nTraitName.methodName() bypasses override resolution and goes directly to the specified trait.\n\nSee Q1131 for diamond resolution. See Q1129 for basic delegation.","ek9Example":"defines module qa.sealedtraits.traitexplicitcall\n\n  defines trait\n\n    LeftInfo\n      getInfo() as pure\n        <- rtn as String: \"left-info\"\n\n    RightInfo\n      getInfo() as pure\n        <- rtn as String: \"right-info\"\n\n  defines class\n\n    Combined with trait of LeftInfo, RightInfo\n\n      //Must override the conflicting method\n      override getInfo() as pure\n        <- rtn as String: \"combined\"\n\n      //Explicit calls to each trait's version\n      getFromLeft() as pure\n        <- rtn as String: LeftInfo.getInfo()\n\n      getFromRight() as pure\n        <- rtn as String: RightInfo.getInfo()\n\n      default operator ?\n\n  defines program\n\n    TraitExplicitCallDemo()\n      stdout <- Stdout()\n\n      combined <- Combined()\n      stdout.println(`Default: ${combined.getInfo()}`)\n      stdout.println(`Left: ${combined.getFromLeft()}`)\n      stdout.println(`Right: ${combined.getFromRight()}`)","migrationContext":"Java: InterfaceName.super.method(). C#: ((IInterface)this).Method(). EK9: TraitName.method() — direct, no casting needed.","keywords":["TraitName","call","disambiguate","explicit","specific","trait"],"primaryTopics":["explicit trait call","trait method disambiguation"],"typicalErrors":[{"error":"E01010","correct":"        <- rtn as String: LeftInfo.getInfo()","incorrect":"        <- rtn as String: ((LeftInfo)this).getInfo()","explanation":"EK9 has no casting. Use TraitName.methodName() to call a specific trait's implementation directly."}],"companions":[]}
{"id":1133,"category":"Sealed Types and Traits","question":"Delegate operators through a trait so the delegating class supports $ and ?.","url":"https://ek9.io/qa/QA1133.html","alternatePhrasings":["I need a wrapper class that delegates not just methods but also operators via a trait","In Kotlin 'by' delegates interface methods. Show EK9 trait operator delegation","Given a trait with operators, create a delegating class that forwards them","Make a delegating wrapper support $ string and ? isSet through its delegate"],"answer":"Trait delegation with 'by' forwards methods AND operators:\n\n  Wrapper with trait of Printable by delegate\n    delegate as Printable?\n\nIf the trait defines operators (via default operator or explicit), the delegating class forwards them to the delegate.\n\nSee Q1129 for basic delegation. See Q1130 for chained delegation.","ek9Example":"defines module qa.sealedtraits.traitoperatordelegation\n\n  defines trait\n\n    Printable\n      describe() as pure\n        <- rtn as String: \"default\"\n\n  defines class\n\n    RealPrinter with trait of Printable\n      override describe() as pure\n        <- rtn as String: \"RealPrinter\"\n      default operator ?\n\n    //Delegates all trait members including operators\n    PrinterWrapper with trait of Printable by delegate\n      delegate as Printable?\n\n      default private PrinterWrapper()\n\n      PrinterWrapper()\n        -> printer as Printable\n        require printer?\n        delegate := printer\n\n      default operator ?\n\n  defines program\n\n    TraitOperatorDelegationDemo()\n      stdout <- Stdout()\n\n      real <- RealPrinter()\n      wrapper <- PrinterWrapper(real)\n\n      //Delegated method call\n      stdout.println(wrapper.describe())\n\n      //Operator ? works through delegation\n      stdout.println(`Wrapper set?: ${wrapper?}`)","migrationContext":"Kotlin: class Wrapper(p: Printable): Printable by p — delegates methods and properties. EK9: 'with trait of X by field' — delegates methods and operators.","keywords":["$","?","by","delegation","forward","operator","trait"],"primaryTopics":["trait operator delegation","operator forwarding"],"typicalErrors":[{"error":"E01010","correct":"Wrapper with trait of Printable by delegate\n      delegate as Printable?","incorrect":"    Wrapper with trait of Printable\n      delegate as Printable\n      operator $ as pure <- rtn as String: $delegate","explanation":"Use 'by delegate' to automatically forward all trait members including operators. No manual forwarding needed."}],"companions":[]}
{"id":1134,"category":"Streams and Pipelines","question":"Filter positive numbers, double them, and sort the result.","url":"https://ek9.io/qa/QA1134.html","alternatePhrasings":["I have a list with negative values and need to keep only positives, transform, then sort","In Java I'd use stream().filter().map().sorted(). Write the EK9 pipeline","Given mixed integers, build a pipeline: filter positives, double each, sort ascending","Chain filter, map, and sort operations in a single stream pipeline"],"answer":"Chain pipe-separated stages left to right:\n  cat items | filter by isPositive | map by doubleIt | sort > stdout\n\ncat creates the stream, each | adds an operation, > stdout terminates. See Q235, Q1073.","ek9Example":"defines module qa.streams.filtermapsort\n\n  defines function\n\n    isPositive() as pure\n      -> item as Integer\n      <- rtn as Boolean: item > 0\n\n    doubleIt() as pure\n      -> item as Integer\n      <- rtn as Integer: item * 2\n\n  defines program\n\n    FilterMapSortDemo()\n      stdout <- Stdout()\n\n      items <- [3, -1, 4, -5, 2, -3, 1]\n\n      cat items\n        | filter by isPositive\n        | map by doubleIt\n        | sort\n        > stdout","migrationContext":"Java: stream().filter(x -> x > 0).map(x -> x * 2).sorted(). Python: sorted(x*2 for x in items if x > 0). EK9: cat items | filter by pred | map by fn | sort.","keywords":["chain","filter","map","pipeline","sort","stream"],"primaryTopics":["filter map sort pipeline","stream chaining"],"typicalErrors":[{"error":"E01010","correct":"cat items\n        | filter by isPositive\n        | map by doubleIt\n        | sort\n        > stdout","incorrect":"      items.stream().filter(isPositive).map(doubleIt).sorted()","explanation":"EK9 uses 'cat source | operation | operation > output' syntax, not method chaining."}],"companions":[]}
{"id":1135,"category":"Streams and Pipelines","question":"Sort a list of Item records by price and collect the sorted result.","url":"https://ek9.io/qa/QA1135.html","alternatePhrasings":["I have product objects and need to sort them by price using a stream","In Java I'd use stream().sorted(). Write the EK9 record sort pipeline","Given a list of items with names and prices, sort and output them","Build a stream pipeline that sorts custom records by their natural ordering"],"answer":"Define 'default operator' on the class, then pipe through sort:\n  sorted <- cat items | sort | collect as List of Item\n\n'default operator' generates <=> comparing fields in declaration order. See Q1085, Q1073.","ek9Example":"defines module qa.streams.streamrecordpipeline\n\n  defines class\n\n    Item\n      name <- String()\n      price <- Float()\n\n      Item()\n        ->\n          name as String\n          price as Float\n        this.name :=: name\n        this.price :=: price\n\n      default operator\n\n  defines program\n\n    StreamRecordPipelineDemo()\n      stdout <- Stdout()\n\n      items <- List() of Item\n      items += Item(\"Monitor\", 299.99)\n      items += Item(\"Mouse\", 19.99)\n      items += Item(\"Keyboard\", 49.99)\n\n      //Sort by natural ordering (name first, then price)\n      sorted <- cat items | sort | collect as List of Item\n      stdout.println(\"Sorted:\")\n      cat sorted > stdout","migrationContext":"Java: stream().sorted(Comparator.comparing(Item::getPrice)). Python: sorted(items, key=lambda x: x.price). EK9: cat items | sort | collect.","keywords":["collect","order","pipeline","record","sort","stream"],"primaryTopics":["record sort pipeline","stream sort with records"],"typicalErrors":[{"error":"E50001","correct":"      sorted <- cat items | sort | collect as List of Item","incorrect":"      items.sort(Comparator.comparing(Item::getPrice))","explanation":"EK9 uses stream pipelines for sorting. Define <=> on the class and use cat | sort."}],"companions":[]}
{"id":1136,"category":"Streams and Pipelines","question":"Collect filtered stream results into a new List.","url":"https://ek9.io/qa/QA1136.html","alternatePhrasings":["I need to capture a pipeline's output as a List instead of printing it","In Java I'd use .collect(Collectors.toList()). Write the EK9 equivalent","Given a filtered stream, materialise the results into a List of Integer","Store the output of a stream pipeline in a variable using collect as"],"answer":"Use 'collect as' to materialise a stream into a list:\n  positives <- cat items | filter by isPositive | collect as List of Integer\n\nThe result is a new List containing all items that passed the pipeline. See Q954, Q1134.","ek9Example":"defines module qa.streams.streamcollectas\n\n  defines function\n\n    isPositive() as pure\n      -> item as Integer\n      <- rtn as Boolean: item > 0\n\n  defines program\n\n    StreamCollectAsDemo()\n      stdout <- Stdout()\n\n      items <- [10, -5, 20, -3, 30, -1]\n\n      //Collect filtered results into a list\n      positives <- cat items\n        | filter by isPositive\n        | collect as List of Integer\n\n      stdout.println(`Collected ${$ length positives} items`)\n      cat positives > stdout","migrationContext":"Java: .collect(Collectors.toList()). Python: list(filter(...)). Rust: .collect::<Vec<_>>(). EK9: | collect as List of T.","keywords":["capture","collect","list","materialise","pipeline","result"],"primaryTopics":["collect as","stream materialisation"],"typicalErrors":[],"companions":[]}
{"id":1137,"category":"Streams and Pipelines","question":"Remove duplicate integers from a list using sort and uniq.","url":"https://ek9.io/qa/QA1137.html","alternatePhrasings":["I have a list with repeated values and need to keep only unique items","In Python I'd use set() for dedup. Write the EK9 stream deduplication","Given [3,1,2,1,3,2], produce a sorted list with no duplicates","Deduplicate a list using a sort | uniq pipeline"],"answer":"Sort first, then uniq removes consecutive duplicates:\n  cat items | sort | uniq > stdout\n\nSort ensures equal items are adjacent so uniq can remove them. See Q989, Q891.","ek9Example":"defines module qa.streams.streamuniqgroup\n\n  defines program\n\n    StreamUniqGroupDemo()\n      stdout <- Stdout()\n\n      items <- [3, 1, 2, 1, 3, 2, 1]\n\n      //Sort then uniq to deduplicate\n      stdout.println(\"Sorted unique:\")\n      cat items | sort | uniq > stdout\n\n      //Sort, group, then flatten\n      stdout.println(\"Sort group flatten:\")\n      cat items | sort | group | flatten > stdout","migrationContext":"Java: stream().distinct().sorted(). Python: sorted(set(items)). Rust: dedup() after sort. EK9: sort | uniq — two-stage pipeline.","keywords":["deduplicate","distinct","flatten","group","sort","uniq","unique"],"primaryTopics":["uniq deduplication","sort uniq pipeline"],"typicalErrors":[{"error":"E01010","correct":"      cat items | sort | uniq > stdout","incorrect":"      cat items | distinct > stdout","explanation":"EK9 uses 'uniq' (not 'distinct'). Sort first to ensure equal items are adjacent."}],"companions":[]}
{"id":1138,"category":"Streams and Pipelines","question":"Take the first 3, last 2, and skip the first 2 items from a list.","url":"https://ek9.io/qa/QA1138.html","alternatePhrasings":["I need to slice a stream: first N, last N, or drop the first N items","In Python I'd use list[:3], list[-2:], list[2:]. Write the EK9 equivalents","Given [10,20,30,40,50], demonstrate head, tail, and skip operations","Paginate a stream by taking or skipping a fixed number of items"],"answer":"head takes first N, tail takes last N, skip drops first N:\n  cat items | head 3 > stdout\n  cat items | tail 2 > stdout\n  cat items | skip 2 > stdout\n\nCombine them: cat items | skip 1 | head 3 > stdout. See Q931.","ek9Example":"defines module qa.streams.streamheadtailskip\n\n  defines program\n\n    StreamHeadTailSkipDemo()\n      stdout <- Stdout()\n\n      items <- [10, 20, 30, 40, 50]\n\n      stdout.println(\"Head 3:\")\n      cat items | head 3 > stdout\n\n      stdout.println(\"Tail 2:\")\n      cat items | tail 2 > stdout\n\n      stdout.println(\"Skip 2:\")\n      cat items | skip 2 > stdout\n\n      stdout.println(\"Skip 1 then Head 3:\")\n      cat items | skip 1 | head 3 > stdout","migrationContext":"Java: stream().limit(3), stream().skip(2). Python: list[:3], list[2:]. Rust: .take(3), .skip(2). EK9: | head 3, | skip 2, | tail 2.","keywords":["first","head","last","paginate","skip","slice","tail","take"],"primaryTopics":["head tail skip","stream slicing"],"typicalErrors":[{"error":"E50060","correct":"      cat items | head 3 > stdout","incorrect":"      cat items | limit(3) > stdout","explanation":"EK9 uses 'head N' not limit(). Similarly 'tail N' and 'skip N'."}],"companions":[]}
{"id":1139,"category":"Streams and Pipelines","question":"Sum a list of integers using join with an addition function.","url":"https://ek9.io/qa/QA1139.html","alternatePhrasings":["I need to reduce a stream to a single value by adding all items together","In Java I'd use stream().reduce(). Write the EK9 join equivalent","Given [10, 20, 30], produce the sum using a stream join operation","Aggregate stream items into a single result using join with a binary function"],"answer":"join reduces a stream to a single value via a binary function:\n  total <- cat [10, 20, 30] | join with addIntegers | collect as Integer\n\nThe binary function takes two items and returns one. See Q1137, Q1134.","ek9Example":"defines module qa.streams.streamjoinsplit\n\n  defines function\n\n    addIntegers() as pure\n      ->\n        a as Integer\n        b as Integer\n      <- rtn as Integer: a + b\n\n    isEven() as pure\n      -> item as Integer\n      <- rtn as Boolean: item mod 2 == 0\n\n  defines program\n\n    StreamJoinSplitDemo()\n      stdout <- Stdout()\n\n      //Join (reduce) to sum\n      total <- cat [10, 20, 30] | join with addIntegers | collect as Integer\n      stdout.println(`Sum: ${total}`)\n\n      //Split by predicate, then flatten\n      stdout.println(\"Split by even:\")\n      cat [1, 2, 3, 4, 5, 6]\n        | split with isEven\n        | flatten\n        > stdout","migrationContext":"Java: stream().reduce(Integer::sum). Python: functools.reduce(). Rust: .fold(). EK9: | join with fn | collect as T.","keywords":["aggregate","binary function","join","reduce","split","sum"],"primaryTopics":["join reduction","split operation"],"typicalErrors":[],"companions":[]}
{"id":1140,"category":"Streams and Pipelines","question":"Capture intermediate sorted results into a side list using tee.","url":"https://ek9.io/qa/QA1140.html","alternatePhrasings":["I need to save a snapshot of the stream at a mid-pipeline stage","In Unix I'd use tee to copy output to a file while continuing the pipeline. Show the EK9 tee","Given a sort | head pipeline, capture the sorted items before head truncates them","Tap into a stream pipeline to collect intermediate results without disrupting flow"],"answer":"tee copies items to a side list without disrupting the pipeline:\n  cat items | sort | tee in sideList | head 3 > stdout\n\nsideList gets all sorted items; the main pipeline continues to head 3. See Q980, Q1138.","ek9Example":"defines module qa.streams.streamteecapture\n\n  defines program\n\n    StreamTeeCaptureDemo()\n      stdout <- Stdout()\n\n      sideList <- List() of Integer\n\n      //Tee captures sorted items before head truncates\n      stdout.println(\"Pipeline output (head 3):\")\n      cat [5, 3, 1, 4, 2]\n        | sort\n        | tee in sideList\n        | head 3\n        > stdout\n\n      //Side list has ALL sorted items, not just head 3\n      stdout.println(\"Side captured (all sorted):\")\n      cat sideList > stdout","migrationContext":"Unix: cmd | tee file.txt | next. Java: peek() in streams. EK9: | tee in list | continues pipeline.","keywords":["capture","intermediate","side list","snapshot","tap","tee"],"primaryTopics":["tee operation","intermediate capture"],"typicalErrors":[],"companions":[]}
{"id":1141,"category":"Streams and Pipelines","question":"Filter a list of fruit names to those longer than 4 characters, then uppercase them.","url":"https://ek9.io/qa/QA1141.html","alternatePhrasings":["I have a list of strings and need to keep only long ones and convert to uppercase","In Python I'd use [s.upper() for s in fruits if len(s) > 4]. Write the EK9 pipeline","Given fruit names, filter by length then transform to uppercase using filter | map","Process a list of strings through a multi-stage pipeline with predicate and transform"],"answer":"Works with String elements the same as any type:\n  cat fruits | filter by isLong | map by toUpper > stdout\n\nUse 'reject by' for the inverse filter: cat fruits | reject by isLong > stdout. See Q1134, Q980.","ek9Example":"defines module qa.streams.streamstringpipeline\n\n  defines function\n\n    isLong() as pure\n      -> item as String\n      <- rtn as Boolean?\n      minLength <- 4\n      rtn: length item > minLength\n\n    toUpper() as pure\n      -> item as String\n      <- rtn as String: item.upperCase()\n\n  defines program\n\n    StreamStringPipelineDemo()\n      stdout <- Stdout()\n\n      fruits <- [\"Banana\", \"Apple\", \"Cherry\", \"Date\", \"Elderberry\"]\n\n      stdout.println(\"Long fruits uppercased:\")\n      cat fruits\n        | filter by isLong\n        | map by toUpper\n        > stdout\n\n      stdout.println(\"Short fruits (rejected by isLong):\")\n      cat fruits\n        | reject by isLong\n        > stdout","migrationContext":"Java: stream().filter(s -> s.length() > 4).map(String::toUpperCase). Python: list comprehension with filter. EK9: | filter by pred | map by fn.","keywords":["filter","map","pipeline","reject","string","uppercase"],"primaryTopics":["string stream pipeline","filter and map"],"typicalErrors":[{"error":"E50060","correct":"cat fruits\n        | filter by isLong\n        | map by toUpper\n        > stdout","incorrect":"      fruits.stream().filter(s -> s.length() > 4).map(s -> s.toUpperCase())","explanation":"EK9 uses named functions with 'filter by' and 'map by', not lambdas."}],"companions":[]}
{"id":1142,"category":"Streams and Pipelines","question":"Sum the prices of all products over 20 using a stream pipeline.","url":"https://ek9.io/qa/QA1142.html","alternatePhrasings":["Write code to filter expensive products and sum their prices in a stream","I have a list of products and need the total price of those costing more than 20","Given a product list, compute the sum of prices above a threshold using a pipeline","In Java I'd use stream().filter().mapToDouble().sum(). Write the EK9 equivalent"],"answer":"Filter, map to price, then join with an addition function:\n  total <- cat products | filter by isExpensive | map with getPrice | join with addFloats | collect as Float\n\nThe join operation reduces a stream to a single value via a binary function. Each pipeline stage uses a named pure function. See Q1139 for join basics. See Q235 for stream operations reference.","ek9Example":"defines module qa.streams.sumaggregation\n\n  defines class\n\n    Product\n      name as String?\n      price as Float?\n\n      default private Product() as pure\n\n      Product() as pure\n        ->\n          name as String\n          price as Float\n        this.name :=? name\n        this.price :=? price\n\n      name() as pure\n        <- rtn as String: String(name)\n\n      price() as pure\n        <- rtn as Float: Float(price)\n\n      operator $ as pure\n        <- rtn as String: `${name} \\$${price}`\n\n      operator #? as pure\n        <- rtn as Integer: #?name\n\n      override operator ? as pure\n        <- rtn as Boolean: name? and price?\n\n  defines function\n\n    isExpensive() as pure\n      -> product as Product\n      <- rtn as Boolean?\n      expensiveThreshold <- 20.0\n      rtn: product.price() > expensiveThreshold\n\n    getPrice() as pure\n      -> product as Product\n      <- rtn as Float: product.price()\n\n    addFloats() as pure\n      ->\n        a as Float\n        b as Float\n      <- rtn as Float: a + b\n\n  defines program\n\n    StreamSumDemo()\n      stdout <- Stdout()\n\n      products <- [\n        Product(\"Widget\", 15.0),\n        Product(\"Gadget\", 45.0),\n        Product(\"Gizmo\", 30.0),\n        Product(\"Trinket\", 5.0),\n        Product(\"Device\", 60.0)\n        ]\n\n      // Filter to expensive products, extract price, sum\n      total <- cat products | filter by isExpensive | map with getPrice | join with addFloats | collect as Float\n      stdout.println(`Total of expensive items: ${total}`)\n\n      // Count expensive items by collecting as list and measuring length\n      expensiveList <- cat products | filter by isExpensive | collect as List of Product\n      stdout.println(`Number of expensive items: ${length expensiveList}`)","migrationContext":"Java: stream().filter(p -> p.price > 20).mapToDouble(Product::getPrice).sum(). Python: sum(p.price for p in products if p.price > 20). Rust: iter().filter().map().sum(). EK9: cat | filter by | map with | join with | collect as.","keywords":["aggregate","filter","join","map","pipeline","price","reduce","stream","sum","total"],"primaryTopics":["stream sum","filter and reduce","join with"],"typicalErrors":[{"error":"E50060","correct":"total <- cat products | filter by isExpensive | map with getPrice | join with addFloats | collect as Float","incorrect":"total <- products.stream().filter(p -> p.price() > 20).sum()","explanation":"EK9 uses cat/pipe stream syntax, not Java-style .stream() calls. Reduction uses 'join with' and a binary function. See ek9 -h E50060 for details."},{"error":"E07520","correct":"      <- rtn as Boolean?\n      expensiveThreshold <- 20.0\n      rtn: product.price() > expensiveThreshold","incorrect":"      <- rtn as Float?\n      rtn: product.price()","explanation":"A function used with 'filter by' must return Boolean; making isExpensive return Float triggers E07520 (must return a Boolean). See ek9 -h E07520 for details."}],"companions":[]}
{"id":1143,"category":"Streams and Pipelines","question":"Take the first 3 positive numbers from a mixed list.","url":"https://ek9.io/qa/QA1143.html","alternatePhrasings":["Write code to filter positive numbers and take the first three","I have a list of mixed integers and need only the first 3 positives","Given [-2, 5, -1, 8, 3, -4, 7], produce the first 3 positive values","In Java I'd use stream().filter(n -> n > 0).limit(3). Write the EK9 equivalent"],"answer":"Filter with a predicate, then limit with head:\n  cat items | filter by isPositive | head 3 > stdout\n\nThe head operation takes only the first N items from the stream. Combining filter and head replaces Java's filter().limit() pattern. See Q235 for stream operations. See Q125 for head/tail/skip.","ek9Example":"defines module qa.streams.filterhead\n\n  defines function\n\n    isPositive() as pure\n      -> num as Integer\n      <- rtn as Boolean: num > 0\n\n    intToString() as pure\n      -> num as Integer\n      <- rtn as String: $num\n\n  defines program\n\n    FilterHeadDemo()\n      stdout <- Stdout()\n\n      items <- [-2, 5, -1, 8, 3, -4, 7, 12, -6]\n\n      // Take first 3 positive numbers\n      firstThree <- cat items | filter by isPositive | head 3 | collect as List of Integer\n      stdout.println(`First 3 positives: ${firstThree}`)\n\n      // Stream directly to stdout\n      stdout.println(\"Streamed:\")\n      cat items | filter by isPositive | head 3 | map with intToString > stdout","migrationContext":"Java: stream().filter(n -> n > 0).limit(3). Python: itertools.islice(filter(...)). Rust: iter().filter().take(3). EK9: cat | filter by | head 3.","keywords":["filter","first","head","limit","pipeline","positive","stream","take"],"primaryTopics":["stream head","filter and limit","take first N"],"typicalErrors":[{"error":"E50060","correct":"firstThree <- cat items | filter by isPositive | head 3 | collect as List of Integer","incorrect":"firstThree <- items.stream().filter(isPositive).limit(3).collect()","explanation":"EK9 uses 'head N' for limiting stream output, not .limit(). See ek9 -h E50060 for details."}],"companions":[]}
{"id":1144,"category":"Streams and Pipelines","question":"Filter a list to items that are both positive and even.","url":"https://ek9.io/qa/QA1144.html","alternatePhrasings":["Write code to chain two filter stages in a stream pipeline","I have a list of integers and need only those that are positive AND even","Given a mixed integer list, select values that pass two predicate tests","In Java I'd use stream().filter(n -> n > 0).filter(n -> n % 2 == 0). Write the EK9 equivalent"],"answer":"Chain multiple filter stages with the pipe operator:\n  cat items | filter by isPositive | filter by isEven | collect as List of Integer\n\nEach filter uses a separate pure predicate function. The pipe operator chains stages naturally, like Unix pipes. See Q235 for stream operations. See Q237 for streams vs loops.","ek9Example":"defines module qa.streams.chainfilters\n\n  defines function\n\n    isPositive() as pure\n      -> num as Integer\n      <- rtn as Boolean: num > 0\n\n    isEven() as pure\n      -> num as Integer\n      <- rtn as Boolean: num mod 2 == 0\n\n    intToString() as pure\n      -> num as Integer\n      <- rtn as String: $num\n\n  defines program\n\n    ChainFiltersDemo()\n      stdout <- Stdout()\n\n      items <- [-5, 2, -3, 8, 7, -4, 6, 1, 10, -2, 0]\n\n      // Chain two filters: positive AND even\n      positiveEvens <- cat items | filter by isPositive | filter by isEven | collect as List of Integer\n      stdout.println(`Positive and even: ${positiveEvens}`)\n\n      // Stream directly to stdout\n      stdout.println(\"Streamed:\")\n      cat items | filter by isPositive | filter by isEven | map with intToString > stdout","migrationContext":"Java: stream().filter(pred1).filter(pred2). Python: filter(pred2, filter(pred1, iterable)). Rust: iter().filter().filter(). EK9: cat | filter by pred1 | filter by pred2.","keywords":["chain","even","filter","multiple","pipe","pipeline","positive","predicate","stream"],"primaryTopics":["chained filters","multiple predicates","stream composition"],"typicalErrors":[{"error":"E50030","correct":"<- rtn as Boolean: num > 0","incorrect":"<- rtn as Integer: num > 0","explanation":"Stream filter predicates must return Boolean. A function used with 'filter by' must return Boolean, not Integer. See ek9 -h E50030 for details."}],"companions":[]}
{"id":1145,"category":"Streams and Pipelines","question":"Print all items from a list directly to stdout using a pipeline.","url":"https://ek9.io/qa/QA1145.html","alternatePhrasings":["I need to output every element in a list to the console","In Unix I'd just cat a file. Write the EK9 'cat list to stdout' pattern","Given a list of names, print each one using the > stdout terminal","Stream a list directly to standard output"],"answer":"Simplest pipeline -- each item printed on its own line:\n  cat names > stdout\n\nAdd operations between cat and > stdout as needed. See Q970, Q1134.","ek9Example":"defines module qa.streams.pipelinetostdout\n\n  defines program\n\n    PipelineToStdoutDemo()\n      stdout <- Stdout()\n\n      names <- [\"Alice\", \"Bob\", \"Charlie\", \"Diana\"]\n\n      //Direct output\n      stdout.println(\"All names:\")\n      cat names > stdout\n\n      //Sorted output\n      stdout.println(\"Sorted:\")\n      cat names | sort > stdout","migrationContext":"Unix: cat file. Java: list.forEach(System.out::println). Python: print(*items, sep='\\n'). EK9: cat list > stdout.","keywords":["cat","output","pipeline","print","stdout","terminal"],"primaryTopics":["cat to stdout","stream output"],"typicalErrors":[],"companions":[]}
{"id":1146,"category":"Control Flow","question":"Assign a value from a function only if it returns a set result, using a guard.","url":"https://ek9.io/qa/QA1146.html","alternatePhrasings":["I need to execute a block only when a function returns a meaningful value","In Kotlin I'd use let { } for null-safe access. Write the EK9 guard equivalent","Given a function that might return unset, guard the result before using it","Use an if-guard to safely unwrap an optional result from a function call"],"answer":"Guard with <- declares a variable AND checks isSet in one line:\n\n  if userName <- findUser(1)\n    stdout.println(`Found: ${userName}`)\n  else\n    stdout.println(\"User not found\")\n\n  //Same pattern works in switch, for, while, and try:\n  while conn <- getConnection()\n    process(conn)\n  switch record <- database.lookup(id)\n    case .type == \"USER\"\n      handleUser(record)\n\nIf the right-hand side returns UNSET, the entire block is skipped. The variable only exists inside the guarded block.\n\nSee Q1038 for config defaults. See Q1152 for :=? guarded assignment.","ek9Example":"defines module qa.controlflow.ifguardassignment\n\n  defines function\n\n    findUser() as pure\n      -> userId as Integer\n      <- name as String: String()\n      if userId == 1\n        name: \"Alice\"\n\n  defines program\n\n    IfGuardAssignmentDemo()\n      stdout <- Stdout()\n\n      //Guard: block runs only if findUser returns set\n      if userName <- findUser(1)\n        stdout.println(`Found: ${userName}`)\n\n      //Guard: unset result skips the block\n      if userName2 <- findUser(999)\n        stdout.println(`Found: ${userName2}`)\n      else\n        stdout.println(\"User not found\")","migrationContext":"Kotlin: getName()?.let { name -> println(name) }. Swift: if let name = getName(). EK9: if name <- getName().","keywords":["<-","assignment","guard","if","safe","unset","unwrap"],"primaryTopics":["if guard assignment","guard expression"],"typicalErrors":[],"companions":[]}
{"id":1147,"category":"Control Flow","question":"Dispatch on Shape type to produce a description for Circle, Square, and Triangle.","url":"https://ek9.io/qa/QA1147.html","alternatePhrasings":["Write code to handle different subtypes using EK9's dispatcher pattern","I have a sealed Shape hierarchy and need type-specific behavior for each shape","Given a Shape that could be Circle, Square, or Triangle, dispatch to the correct handler","In Java I'd use instanceof checks. Write the EK9 dispatcher equivalent"],"answer":"Use a dispatcher method on a class with overloads for each type:\n  describe() as dispatcher\n    -> shape as Shape\n    <- rtn as String: \"Unknown\"\n  describe()\n    -> shape as Circle\n    <- rtn as String: \"A circle\"\n\nMark the base method 'as dispatcher'. The compiler generates dispatch logic automatically. No instanceof, no casting, no switch-on-type. This is the ONLY way to do type-based dispatch in EK9. See Q1121 for sealed dispatcher. See Q1122 for dispatcher with fallback.","ek9Example":"defines module qa.controlflow.switchontype\n\n  defines trait\n\n    Shape allow only Circle, Square, Triangle\n      name() as abstract\n        <- rtn as String?\n\n  defines class\n\n    Circle with trait of Shape\n      override name()\n        <- rtn as String: \"Circle\"\n\n    Square with trait of Shape\n      override name()\n        <- rtn as String: \"Square\"\n\n    Triangle with trait of Shape\n      override name()\n        <- rtn as String: \"Triangle\"\n\n    ShapeDescriber\n\n      describe() as dispatcher\n        -> shape as Shape\n        <- rtn as String: \"Unknown shape\"\n\n      describe()\n        -> shape as Circle\n        <- rtn as String: \"A circle with curved edges\"\n\n      describe()\n        -> shape as Square\n        <- rtn as String: \"A square with four equal sides\"\n\n      describe()\n        -> shape as Triangle\n        <- rtn as String: \"A triangle with three sides\"\n\n  defines program\n\n    SwitchOnTypeDemo()\n      stdout <- Stdout()\n      describer <- ShapeDescriber()\n\n      shapes <- List() of Shape\n      shapes += Circle()\n      shapes += Square()\n      shapes += Triangle()\n\n      for shape in shapes\n        description <- describer.describe(shape)\n        stdout.println(description)","migrationContext":"Java: instanceof + cast chain or visitor pattern. Kotlin: sealed class + when(shape) { is Circle -> }. Rust: enum + match. Python: isinstance() checks. EK9: sealed trait + dispatcher — compiler generates dispatch, no casting, no instanceof.","keywords":["dispatch","dispatcher","instanceof","overload","pattern","sealed","shape","trait","type"],"primaryTopics":["dispatcher pattern","type-based dispatch","switch on type"],"typicalErrors":[{"error":"E01010","correct":"describe() as dispatcher\n        -> shape as Shape","incorrect":"if shape instanceof Circle","explanation":"EK9 has no instanceof keyword. Use 'as dispatcher' on a method to dispatch on runtime types. The compiler generates the dispatch logic. See ek9 -h E01010 for details."},{"error":"E50060","correct":"      describe() as dispatcher\n        -> shape as Shape\n        <- rtn as String: \"Unknown shape\"","incorrect":"switch shape.getClass()\n        case Circle\n          rtn: \"A circle\"","explanation":"EK9 has no getClass() method and switch cannot dispatch on types. Use the dispatcher pattern with method overloads for each type. See ek9 -h E50060 for details."}],"companions":[]}
{"id":1148,"category":"Control Flow","question":"Iterate from 10 down to 1 using a descending for-range.","url":"https://ek9.io/qa/QA1148.html","alternatePhrasings":["I need a countdown loop from 10 to 1","In Python I'd use range(10, 0, -1). Write the EK9 descending range","Given start=10 and end=1, iterate downward automatically","Loop in reverse order over a numeric range"],"answer":"EK9 auto-detects descending when start > end:\n  for i in 10 ... 1\n    sum: sum + i\n\nNo explicit step needed. For custom steps use 'by': for i in 10 ... 1 by 2. See Q1149.","ek9Example":"defines module qa.controlflow.forrangedescending\n\n  defines program\n\n    ForRangeDescendingDemo()\n      stdout <- Stdout()\n      sum <- 0\n\n      //Descending: 10 down to 1 automatically\n      for i in 10 ... 1\n        sum: sum + i\n\n      //Sum of 10+9+8+...+1 = 55\n      stdout.println(`Sum: ${sum}`)","migrationContext":"Python: range(10, 0, -1). Java: for(int i=10; i>=1; i--). Go: for i := 10; i >= 1; i--. EK9: for i in 10 ... 1 — automatic descending.","keywords":["countdown","descending","for","loop","range","reverse"],"primaryTopics":["descending for-range","countdown loop"],"typicalErrors":[{"error":"E50001","correct":"      for i in 10 ... 1","incorrect":"      for i in range(10, 0, -1)","explanation":"EK9 uses 'for i in start ... end' with three dots. No range() function. Descending is automatic when start > end."}],"companions":[]}
{"id":1149,"category":"Control Flow","question":"Iterate from 0 to 20 with a step of 5 using for-range with by.","url":"https://ek9.io/qa/QA1149.html","alternatePhrasings":["I need a loop that counts in steps of 5 instead of 1","In Python I'd use range(0, 21, 5). Write the EK9 range with step","Given start=0, end=20, step=5, iterate with a custom increment","Loop through a range with a specific step value using 'by'"],"answer":"'by' specifies the step value, always positive:\n  for i in 0 ... 20 by 5\n    stdout.println($i)\n\nEK9 determines direction from start vs end. Descending: for i in 20 ... 0 by 5. See Q1148.","ek9Example":"defines module qa.controlflow.forrangewithby\n\n  defines program\n\n    ForRangeWithByDemo()\n      stdout <- Stdout()\n\n      //Step by 5\n      stdout.println(\"By 5:\")\n      for i in 0 ... 20 by 5\n        stdout.println($i)","migrationContext":"Python: range(0, 21, 5). Java: for(int i=0; i<=20; i+=5). Go: for i := 0; i <= 20; i += 5. EK9: for i in 0 ... 20 by 5.","keywords":["by","custom","for","increment","range","step"],"primaryTopics":["for-range with by","custom step"],"typicalErrors":[{"error":"E50001","correct":"      for i in 0 ... 20 by 5","incorrect":"      for i in range(0, 20, 5)","explanation":"EK9 uses 'for i in start ... end by step'. No range() function."}],"companions":[]}
{"id":1150,"category":"Control Flow","question":"Loop while a connection value remains set, processing each iteration.","url":"https://ek9.io/qa/QA1150.html","alternatePhrasings":["Write code to use a while guard that loops while a value is set","I have a supplier that eventually returns unset and need to loop until that happens","Given a function that returns set values for N calls then unset, process each value","In Java I'd use while ((val = getNext()) != null). Write the EK9 guard equivalent"],"answer":"Use a while guard with declaration operator:\n  while conn <- getConnection() with counter < maxIters\n    stdout.println(conn)\n    counter++\n\nThe guard re-evaluates on EVERY iteration. If getConnection() returns an unset value, the loop terminates cleanly. The 'with' clause adds an additional condition. See Q77 for while guard details. See Q971 for guard expressions.","ek9Example":"defines module qa.controlflow.whileguard\n\n  defines class\n\n    ConnectionSupplier\n      remaining <- 0\n\n      ConnectionSupplier()\n        -> count as Integer\n        this.remaining: count\n\n      getConnection()\n        <- rtn as String: String()\n        if remaining > 0\n          rtn: `Connection-${remaining}`\n          remaining: remaining - 1\n\n      override operator ? as pure\n        <- rtn as Boolean: remaining?\n\n  defines program\n\n    WhileGuardDemo()\n      stdout <- Stdout()\n\n      supplier <- ConnectionSupplier(4)\n      counter <- 0\n      maxIters <- 10\n\n      // Loop while getConnection() returns a SET value\n      while conn <- supplier.getConnection() with counter < maxIters\n        stdout.println(conn)\n        counter: counter + 1\n\n      stdout.println(`Processed ${counter} connections`)","migrationContext":"Java: while ((val = getNext()) != null) { process(val); }. Go: for val := getNext(); val != nil; val = getNext(). Rust: while let Some(v) = get_next(). EK9: while val <- getNext() — guard re-evaluates each iteration.","keywords":["connection","declaration","guard","isset","loop","poll","unset","while"],"primaryTopics":["while guard","guard loop","polling pattern"],"typicalErrors":[{"error":"E07390","correct":"while conn <- supplier.getConnection() with counter < maxIters","incorrect":"while conn <- supplier.getConnection() with true","explanation":"Using a constant Boolean literal (true) as the while-guard's 'with' condition is a pointless expression; supply a real terminating condition instead. See ek9 -h E07390 for details."},{"error":"E01073","correct":"counter < maxIters","incorrect":"counter <> null","explanation":"'null' does not exist in EK9; use tri-state semantics (unset/set) with the '?' operator and guard expressions instead. See ek9 -h E01073 for details."}],"companions":[]}
{"id":1151,"category":"Control Flow","question":"Map a day of the week to 'weekday' or 'weekend' using a switch with multiple case values.","url":"https://ek9.io/qa/QA1151.html","alternatePhrasings":["I need a switch that groups several values into one case — no fallthrough needed","In Java I'd use switch with fallthrough for grouping. Write the EK9 multi-value case","Given a day name, categorise it using multiple values in a single case line","Handle Monday through Friday in one case and Saturday/Sunday in another"],"answer":"Comma-separated values on one case line, no fallthrough by design:\n  case \"Monday\", \"Tuesday\", \"Wednesday\", \"Thursday\", \"Friday\"\n    category: \"Weekday\"\n  case \"Saturday\", \"Sunday\"\n    category: \"Weekend\"\n\nMultiple case values replace the need for fallthrough entirely. See Q237.","ek9Example":"defines module qa.controlflow.switchmultiplecases\n\n  defines program\n\n    SwitchMultipleCasesDemo()\n      stdout <- Stdout()\n\n      days <- [\"Monday\", \"Saturday\", \"Wednesday\", \"Sunday\"]\n\n      for day in days\n        category <- \"Unknown\"\n        switch day\n          case \"Monday\", \"Tuesday\", \"Wednesday\", \"Thursday\", \"Friday\"\n            category: \"Weekday\"\n          case \"Saturday\", \"Sunday\"\n            category: \"Weekend\"\n          default\n            category: \"Unknown\"\n        stdout.println(`${day}: ${category}`)","migrationContext":"Java: case \"Mon\": case \"Tue\": (fallthrough). C#: case \"Mon\": case \"Tue\": (fallthrough). EK9: case \"Monday\", \"Tuesday\" — comma-separated, no fallthrough.","keywords":["case","group","multiple","switch","values","weekday","weekend"],"primaryTopics":["multi-value case","switch without fallthrough"],"typicalErrors":[{"error":"E01010","correct":"      case \"Monday\", \"Tuesday\", \"Wednesday\"","incorrect":"      case \"Monday\":\n      case \"Tuesday\":\n      case \"Wednesday\":","explanation":"EK9 has no fallthrough and no colons after case. List multiple values with commas on one case line."}],"companions":[]}
{"id":1152,"category":"Control Flow","question":"Set a default port only if one has not already been provided.","url":"https://ek9.io/qa/QA1152.html","alternatePhrasings":["I need to assign a fallback value but only when the variable is unset","In Ruby I'd use ||=. Write the EK9 guarded assignment equivalent","Given an optional port configuration, apply a default if nothing was set","Use :=? to conditionally initialise a variable that might already have a value"],"answer":"Only assigns if the target is currently UNSET:\n  port :=? 8080\n\nIf port is already SET, the assignment is skipped. No if-check needed. See Q1038, Q1091.","ek9Example":"defines module qa.controlflow.guardedassignment\n\n  defines program\n\n    GuardedAssignmentDemo()\n      stdout <- Stdout()\n\n      //Port is unset — :=? assigns the default\n      port <- Integer()\n      port :=? 8080\n      stdout.println(`Port: ${port}`)\n\n      //Port is already set — :=? is skipped\n      port :=? 9090\n      stdout.println(`Still: ${port}`)","migrationContext":"Ruby: port ||= 8080. Kotlin: port = port ?: 8080. JavaScript: port ??= 8080. EK9: port :=? 8080.","keywords":[":=?","assignment","conditional","default","fallback","guarded"],"primaryTopics":[":=? guarded assignment","conditional default"],"typicalErrors":[{"error":"E01073","correct":"      port :=? 8080","incorrect":"      if port == null\n        port = 8080","explanation":"EK9 has no null. Use :=? for conditional assignment — it only sets the value if the target is currently unset."}],"companions":[]}
{"id":1153,"category":"Control Flow","question":"Assign different labels based on a score using if/else.","url":"https://ek9.io/qa/QA1153.html","alternatePhrasings":["Write code to categorize a numeric score into grade labels","I have a score and need to assign Excellent, Good, or Needs Work based on thresholds","Given a score from 0-100, produce a text label based on ranges","In Java I'd use if/else if/else to assign a grade. Write the EK9 equivalent"],"answer":"Declare variable, assign in each branch:\n  label <- \"Unknown\"\n  if score > excellentThreshold\n    label: \"Excellent\"\n  else if score > goodThreshold\n    label: \"Good\"\n  else\n    label: \"Needs work\"\n\nEK9 has no return. Assign in each branch using the : operator. See Q1146 for if-guard.","ek9Example":"defines module qa.controlflow.ifelsevalue\n\n  defines program\n\n    IfElseValueDemo()\n      stdout <- Stdout()\n\n      excellentThreshold <- 90\n      goodThreshold <- 70\n\n      scores <- [95, 85, 72, 60]\n\n      for score in scores\n        label <- \"Unknown\"\n        if score > excellentThreshold\n          label: \"Excellent\"\n        else if score > goodThreshold\n          label: \"Good\"\n        else\n          label: \"Needs work\"\n        stdout.println(`Score ${score}: ${label}`)","migrationContext":"Java: if/else if/else with return. Python: if/elif/else. Rust: let label = if { } else { }. EK9: declare variable, then assign in each branch.","keywords":["assign","branch","else","grade","if","label","score","threshold"],"primaryTopics":["if else assignment","branching"],"typicalErrors":[{"error":"E01072","correct":"        label <- \"Unknown\"\n        if score > excellentThreshold\n          label: \"Excellent\"","incorrect":"if score > 90\n        return \"Excellent\"","explanation":"EK9 has no return statement. Declare a variable before the if/else and assign in each branch."}],"companions":[]}
{"id":1154,"category":"Control Flow","question":"Catch an exception from a division operation and handle it.","url":"https://ek9.io/qa/QA1154.html","alternatePhrasings":["I need to handle a runtime error using try/catch in EK9","In Java I'd use try-catch with specific exception types. Write the EK9 equivalent","Given a division that might fail, catch the exception and print an error message","Handle an arithmetic error gracefully using try and catch blocks"],"answer":"catch uses -> parameter syntax; ex.reason() gets the message:\n\n  try\n    result <- 100 / 5\n    stdout.println(`Result: ${result}`)\n  catch\n    -> ex as Exception\n    stdout.println(`Error: ${ex.reason()}`)\n  finally\n    stdout.println(\"cleanup\")\n\nThe catch block declares the exception with -> (data-in port). The finally block always runs. Use ex.reason() not getMessage().\n\nSee Q1155 for finally details. See Q1156 for nested try/catch.","ek9Example":"defines module qa.controlflow.trycatchbasics\n\n  defines program\n\n    TryCatchBasicsDemo()\n      stdout <- Stdout()\n\n      //Safe division\n      try\n        result <- 100 / 5\n        stdout.println(`100 / 5 = ${result}`)\n      catch\n        -> ex as Exception\n        stdout.println(`Error: ${ex.reason()}`)","migrationContext":"Java: try { } catch (Exception e) { }. Python: try: except Exception as e:. EK9: try / catch -> ex as Exception.","keywords":["catch","divide","error","exception","handle","try"],"primaryTopics":["try catch","exception handling"],"typicalErrors":[{"error":"E01010","correct":"      catch\n        -> ex as Exception\n        stdout.println(`Error: ${ex.reason()}`)","incorrect":"      catch (Exception ex) {\n        System.out.println(ex.getMessage());\n      }","explanation":"EK9 uses indentation, not braces. The catch parameter uses -> syntax. Use .reason() not .getMessage()."}],"companions":[]}
{"id":1155,"category":"Control Flow","question":"Run cleanup code after a try block regardless of whether an exception occurred.","url":"https://ek9.io/qa/QA1155.html","alternatePhrasings":["I need a finally block that always runs after try/catch","In Java I'd use try-finally for guaranteed cleanup. Write the EK9 equivalent","Given an operation that might fail, ensure cleanup runs either way","Execute teardown logic after both success and failure paths"],"answer":"finally always runs after try/catch:\n  try\n    stdout.println(\"working\")\n  catch\n    -> ex as Exception\n    stdout.println(ex.reason())\n  finally\n    stdout.println(\"cleanup\")\n\nThe finally block executes whether the try succeeds or catch handles an exception.\n\nSee Q1154 for basic try/catch.","ek9Example":"defines module qa.controlflow.trywithfinally\n\n  defines program\n\n    TryWithFinallyDemo()\n      stdout <- Stdout()\n\n      //Success path — finally still runs\n      try\n        stdout.println(\"Working\")\n      catch\n        -> ex as Exception\n        stdout.println(ex.reason())\n      finally\n        stdout.println(\"Cleanup runs regardless\")","migrationContext":"Java: try { } catch { } finally { }. Python: try/except/finally. Go: defer. EK9: try/catch/finally — same structure as Java.","keywords":["always","cleanup","exception","finally","teardown","try"],"primaryTopics":["try finally","guaranteed cleanup"],"typicalErrors":[{"error":"E01010","correct":"      finally\n        stdout.println(\"Cleanup runs regardless\")","incorrect":"      finally {\n        System.out.println(\"cleanup\");\n      }","explanation":"EK9 uses indentation, not braces. The finally block is a peer of try and catch."}],"companions":[]}
{"id":1156,"category":"Control Flow","question":"Catch an exception in an inner try block so the outer catch does not fire.","url":"https://ek9.io/qa/QA1156.html","alternatePhrasings":["I need nested error handling where the inner block consumes the exception","In Java nested try-catch handles exceptions at the closest level. Show the EK9 pattern","Given inner and outer try blocks, verify the inner catch prevents outer catch from running","Handle an exception locally in a nested try without propagating to the outer block"],"answer":"Inner catch consumes the exception:\n  try\n    try\n      ex <- Exception(\"inner\")\n      throw ex\n    catch\n      -> e as Exception\n      stdout.println(\"Inner handled\")\n    stdout.println(\"Outer continues\")\n  catch\n    -> e as Exception\n    stdout.println(\"Outer NOT reached\")\n\nOuter catch only fires if the exception is not caught by an inner block.\n\nSee Q1154 for basic try/catch. See Q1155 for finally.","ek9Example":"defines module qa.controlflow.nestedtrycatch\n\n  defines program\n\n    NestedTryCatchDemo()\n      stdout <- Stdout()\n\n      try\n        stdout.println(\"Outer try: start\")\n        try\n          stdout.println(\"Inner try: about to throw\")\n          ex <- Exception(\"Inner exception\")\n          throw ex\n        catch\n          -> e as Exception\n          stdout.println(\"Inner catch: handled\")\n        stdout.println(\"Outer try: continues after inner block\")\n      catch\n        -> e as Exception\n        stdout.println(\"Outer catch: should not execute\")\n\n      stdout.println(\"Done\")","migrationContext":"Java: nested try-catch — same semantics. Python: nested try/except. EK9: nested try/catch — inner catch consumes the exception.","keywords":["catch","exception","inner","nested","outer","propagate","try"],"primaryTopics":["nested try catch","exception consumption"],"typicalErrors":[{"error":"E01010","correct":"      throw ex","incorrect":"      throw new Exception(\"msg\")","explanation":"EK9 has no 'new' keyword. Create the exception first, then throw it: ex <- Exception(\"msg\") / throw ex."}],"companions":[]}
{"id":1157,"category":"Control Flow","question":"Guard a try block so it only runs when the guard value is set.","url":"https://ek9.io/qa/QA1157.html","alternatePhrasings":["I need the try body to execute only if a value is present","In Swift I'd use 'guard let'. Show the EK9 try guard equivalent","Given a possibly-unset value, wrap the try block with a guard assignment","Skip the try body entirely when the guard variable is unset"],"answer":"try with guard — body runs only if guard value is set:\n  try mainValue <- setValue\n    stdout.println(mainValue)\n  catch\n    -> e as Exception\n    stdout.println(e.reason())\n  finally\n    stdout.println(\"cleanup\")\n\nIf setValue is unset, the entire try body is skipped. Finally still runs.\n\nSee Q1146 for if-guard. See Q1154 for basic try/catch.","ek9Example":"defines module qa.controlflow.tryguardexpression\n\n  defines program\n\n    TryGuardExpressionDemo()\n      stdout <- Stdout()\n\n      //Set guard — try body executes\n      setValue <- 42\n      try mainValue <- setValue\n        stdout.println(`Try body: ${mainValue}`)\n      catch\n        -> e as Exception\n        stdout.println(e.reason())\n      finally\n        stdout.println(\"Finally: always runs\")\n\n      //Unset guard — try body skipped\n      unsetValue <- Integer()\n      try skipped <- unsetValue\n        stdout.println(\"Should not print\")\n      catch\n        -> e as Exception\n        stdout.println(\"Should not print\")\n      finally\n        stdout.println(\"Finally on unset guard: still runs\")","migrationContext":"Swift: guard let value = optional else { return }. Kotlin: value?.let { }. EK9: try value <- expr — guard on the try itself.","keywords":["conditional","guard","set","skip","try","unset"],"primaryTopics":["try guard","conditional try execution"],"typicalErrors":[{"error":"E01073","correct":"setValue <- 42","incorrect":"setValue <- null","explanation":"EK9 has no null literal — 'setValue <- null' is rejected outright; use tri-state semantics (an unset value like Integer()) with the '?' operator instead. See ek9 -h E01073 for details."}],"companions":[]}
{"id":1158,"category":"Collections and Data Structures","question":"Iterate over a Dict and print each key-value pair.","url":"https://ek9.io/qa/QA1158.html","alternatePhrasings":["I need to loop through all entries in a dictionary","In Python I'd use for k, v in dict.items(). Write the EK9 Dict iteration","Given a Dict of names to ages, print each entry using for-in","Traverse a Dict and access each key-value pair as a DictEntry"],"answer":"for-in iterates over DictEntry objects:\n  for entry in ages\n    stdout.println($entry)\n\nEach entry displays as key=value. See Q1161 for full Dict operations.","ek9Example":"defines module qa.collections.dictiteration\n\n  defines program\n\n    DictIterationDemo()\n      stdout <- Stdout()\n\n      ages <- Dict() of (String, Integer)\n      ages += DictEntry(\"Alice\", 30)\n      ages += DictEntry(\"Bob\", 25)\n      ages += DictEntry(\"Charlie\", 35)\n\n      //Print the whole Dict\n      stdout.println($ages)\n\n      //Iterate over entries\n      for entry in ages\n        stdout.println($entry)","migrationContext":"Python: for k, v in dict.items(). Java: for (Map.Entry<K,V> e : map.entrySet()). Go: for k, v := range m. EK9: for entry in dict.","keywords":["Dict","entry","for-in","iterate","key-value","loop"],"primaryTopics":["Dict iteration","DictEntry"],"typicalErrors":[{"error":"E01010","correct":"      ages <- Dict() of (String, Integer)","incorrect":"      ages <- Dict() of String, Integer","explanation":"Dict type parameters need parentheses: Dict() of (K, V)."}],"companions":[]}
{"id":1159,"category":"Collections and Data Structures","question":"Check if a country code is in a list of approved countries.","url":"https://ek9.io/qa/QA1159.html","alternatePhrasings":["Write code to check list membership in EK9","I have a list of valid codes and need to verify a value is in that list","Given a list of approved strings, determine if a candidate is present","In Java I'd use list.contains(). Write the EK9 equivalent"],"answer":"Use the contains operator on a List:\n  if approved contains countryCode\n    stdout.println(\"Approved\")\n\nThe 'contains' operator checks whether a value exists in a List or Dict. For List, it checks element membership. For Dict, it checks key existence. See Q45 for List basics. See Q129 for Dict contains.","ek9Example":"defines module qa.collections.listcontains\n\n  defines program\n\n    ListContainsDemo()\n      stdout <- Stdout()\n\n      approved <- [\"GB\", \"US\", \"DE\", \"FR\", \"JP\"]\n\n      // Check membership with contains operator\n      countryCode <- \"GB\"\n      if approved contains countryCode\n        stdout.println(`${countryCode} is approved`)\n\n      // Check a code that is not in the list\n      unknownCode <- \"ZZ\"\n      if not (approved contains unknownCode)\n        stdout.println(`${unknownCode} is not approved`)\n\n      // Check multiple codes\n      candidates <- [\"US\", \"BR\", \"FR\", \"CN\"]\n      for candidate in candidates\n        if approved contains candidate\n          stdout.println(`${candidate}: approved`)\n        else\n          stdout.println(`${candidate}: rejected`)","migrationContext":"Java: list.contains(item). Python: item in list. Rust: vec.contains(&item). Go: manual loop (no built-in). JavaScript: array.includes(item). EK9: list contains item — operator syntax, not method call.","keywords":["approved","check","collection","contains","in","list","lookup","membership"],"primaryTopics":["list contains","membership check","contains operator"],"typicalErrors":[{"error":"E07620","correct":"if approved contains countryCode","incorrect":"if approved contains 42","explanation":"'contains' on a List of String is not defined for an Integer operand — the argument must match the element type. See ek9 -h E07620 for details."}],"companions":[]}
{"id":1160,"category":"Collections and Data Structures","question":"Add, access, and remove items from a List of strings.","url":"https://ek9.io/qa/QA1160.html","alternatePhrasings":["I need to perform basic List operations: add, get, remove, check length","In Java I'd use ArrayList methods. Write the EK9 List equivalents","Given a List of names, demonstrate add with +=, access, and length","Show the core List operations on a collection of strings"],"answer":"List operations use operators and methods:\n  names <- List() of String\n  names += \"Alice\"\n  names += \"Bob\"\n  stdout.println($ length names)\n  cat names > stdout\n\n+= adds items. 'length' prefix operator returns size. 'cat list > stdout' prints all items.\n\nSee Q1158 for Dict iteration. See Q1081 for list merge.","ek9Example":"defines module qa.collections.listcomprehensive\n\n  defines program\n\n    ListComprehensiveDemo()\n      stdout <- Stdout()\n\n      names <- List() of String\n      names += \"Alice\"\n      names += \"Bob\"\n      names += \"Charlie\"\n\n      //Length\n      stdout.println(`Length: ${$ length names}`)\n\n      //Print all items\n      cat names > stdout\n\n      //List literal shorthand\n      numbers <- [10, 20, 30]\n      stdout.println(`Numbers: ${numbers}`)","migrationContext":"Java: list.add(), list.size(), list.get(). Python: list.append(), len(list). EK9: += to add, length prefix, cat to iterate.","keywords":["List","access","add","length","operations","remove"],"primaryTopics":["List operations","List basics"],"typicalErrors":[{"error":"E50060","correct":"      names += \"Alice\"","incorrect":"      names.add(\"Alice\")","explanation":"EK9 uses += operator to add items to a list, not .add() method."}],"companions":[]}
{"id":1161,"category":"Collections and Data Structures","question":"Add entries to a Dict, look up a value by key, and iterate over all entries.","url":"https://ek9.io/qa/QA1161.html","alternatePhrasings":["I need to build a dictionary, retrieve values, and loop through entries","In Python I'd use dict[key] = value and for k,v in dict.items(). Write the EK9 Dict","Given a Dict of country codes to names, add entries, look up, and iterate","Show the core Dict operations: add with DictEntry, getOrDefault, and for-in"],"answer":"Dict operations use DictEntry, getOrDefault, and for-in:\n  codes <- Dict() of (String, String)\n  codes += DictEntry(\"GB\", \"United Kingdom\")\n  result <- codes.getOrDefault(\"GB\", \"Unknown\")\n  for entry in codes\n    stdout.println($entry)\n\nDict type uses (K, V) parentheses. getOrDefault returns the value or a fallback.\n\nSee Q1158 for Dict iteration. See Q1095 for custom Dict keys.","ek9Example":"defines module qa.collections.dictcomprehensive\n\n  defines program\n\n    DictComprehensiveDemo()\n      stdout <- Stdout()\n\n      codes <- Dict() of (String, String)\n      codes += DictEntry(\"GB\", \"United Kingdom\")\n      codes += DictEntry(\"US\", \"United States\")\n      codes += DictEntry(\"DE\", \"Germany\")\n\n      //Lookup by key\n      result <- codes.getOrDefault(\"GB\", \"Unknown\")\n      stdout.println(`GB: ${result}`)\n\n      //Missing key — returns default\n      missing <- codes.getOrDefault(\"FR\", \"Not found\")\n      stdout.println(`FR: ${missing}`)\n\n      //Iterate\n      for entry in codes\n        stdout.println($entry)","migrationContext":"Python: dict[key] = value, dict.get(key, default). Java: map.put(), map.getOrDefault(). EK9: += DictEntry(), getOrDefault().","keywords":["Dict","DictEntry","add","getOrDefault","iterate","lookup"],"primaryTopics":["Dict operations","Dict basics"],"typicalErrors":[{"error":"E01010","correct":"      codes <- Dict() of (String, String)","incorrect":"      codes <- Dict() of String, String","explanation":"Dict type parameters must be in parentheses: Dict() of (K, V)."}],"companions":[]}
{"id":1162,"category":"Collections and Data Structures","question":"Remove all negative numbers from a list, keeping only positives.","url":"https://ek9.io/qa/QA1162.html","alternatePhrasings":["Write code to filter out unwanted elements from a list","I have a list of integers and need a new list with only positive values","Given a list with mixed positive and negative numbers, produce a positives-only list","In Java I'd use stream().filter(n -> n > 0).collect(). Write the EK9 equivalent"],"answer":"Use a stream pipeline to filter and collect into a new list:\n  positives <- cat numbers | filter by isPositive | collect as List of Integer\n\nEK9 does not have a .removeIf() method on lists. Filtering is done through stream pipelines, creating a new collection with only the desired elements. See Q235 for stream operations. See Q122 for collect as.","ek9Example":"defines module qa.collections.removematching\n\n  defines function\n\n    isPositive() as pure\n      -> num as Integer\n      <- rtn as Boolean: num > 0\n\n    intToString() as pure\n      -> num as Integer\n      <- rtn as String: $num\n\n  defines program\n\n    RemoveMatchingDemo()\n      stdout <- Stdout()\n\n      numbers <- [5, -3, 8, -1, 4, -7, 2, -9, 10]\n      stdout.println(`Original: ${numbers}`)\n\n      // Filter to keep only positives\n      positives <- cat numbers | filter by isPositive | collect as List of Integer\n      stdout.println(`Positives only: ${positives}`)\n\n      // Stream filtered results directly\n      stdout.println(\"Streamed positives:\")\n      cat numbers | filter by isPositive | map with intToString > stdout","migrationContext":"Java: list.removeIf(n -> n < 0) or stream().filter().collect(). Python: [n for n in nums if n > 0]. Rust: vec.retain(|n| *n > 0). Go: manual loop. EK9: cat | filter by | collect as — always creates a new list.","keywords":["collect","filter","list","negative","pipeline","positive","remove","stream"],"primaryTopics":["filter to new list","remove matching elements","stream collect"],"typicalErrors":[{"error":"E50001","correct":"positives <- cat numbers | filter by isPositive | collect as List of Integer","incorrect":"numbers.removeIf(isNegative)","explanation":"EK9 lists do not have a removeIf() method. Use a stream pipeline with filter and collect to create a new list with the desired elements. See ek9 -h E50060 for details."}],"companions":[]}
{"id":1163,"category":"Collections and Data Structures","question":"Find the common elements between two lists of names.","url":"https://ek9.io/qa/QA1163.html","alternatePhrasings":["Write code to compute the intersection of two lists","I have teamA and teamB lists and need the members in both","Given two lists, filter one to only items that appear in the other","In Python I'd use set(a) & set(b). Write the EK9 list intersection"],"answer":"Filter one list by membership in the other:\n  common <- cat teamA\n    | filter by isInTeamB\n    | collect as List of String\n\nUse a dynamic function capturing the second list and checking 'is in'.\n\nSee Q1101 for 'is in'. See Q1136 for collect as.","ek9Example":"defines module qa.collections.listintersection\n\n  defines function\n\n    InListCheck as pure abstract\n      -> name as String\n      <- rtn as Boolean?\n\n  defines program\n\n    ListIntersectionDemo()\n      stdout <- Stdout()\n\n      teamA <- [\"Alice\", \"Bob\", \"Charlie\", \"Diana\"]\n      teamB <- [\"Charlie\", \"Eve\", \"Alice\", \"Frank\"]\n\n      //Dynamic function capturing teamB for the membership check\n      isInTeamB <- (teamB) extends InListCheck as pure function\n        rtn: name is in teamB\n\n      //Filter teamA to only those also in teamB\n      common <- cat teamA | filter by isInTeamB | collect as List of String\n      stdout.println(`Common members: ${common}`)","migrationContext":"Python: set(a) & set(b) or [x for x in a if x in b]. Java: a.stream().filter(b::contains). EK9: cat a | filter by capturedCheck | collect.","keywords":["both","common","filter","intersection","is in","lists"],"primaryTopics":["list intersection","filter by membership"],"typicalErrors":[{"error":"E50060","correct":"common <- cat teamA | filter by isInTeamB | collect as List of String","incorrect":"common <- teamA.retainAll(teamB)","explanation":"EK9 List has no retainAll() method; compute an intersection with a filter pipeline using 'is in'. See ek9 -h E50060 for details."}],"companions":[]}
{"id":1164,"category":"Operators and Expressions","question":"Build a formatted string with embedded expressions using backtick interpolation.","url":"https://ek9.io/qa/QA1164.html","alternatePhrasings":["I need to embed variable values and arithmetic inside a string","In JavaScript I'd use template literals with ${}. Write the EK9 interpolation","Given name, age, and a calculation, build a formatted output string","Construct a display string with multiple embedded expressions"],"answer":"Backtick strings with ${expr} for interpolation:\n  stdout.println(`Name: ${name}, Age: ${age}`)\n  stdout.println(`Total: ${price * quantity}`)\n\n$ inside backticks is the interpolation marker, not the $ string operator. See Q909, Q918.","ek9Example":"defines module qa.operators.interpolationpatterns\n\n  defines program\n\n    InterpolationPatternsDemo()\n      stdout <- Stdout()\n\n      name <- \"Alice\"\n      age <- 30\n      price <- 9.99\n      quantity <- 3\n\n      //Basic interpolation\n      stdout.println(`Name: ${name}, Age: ${age}`)\n\n      //Arithmetic inside ${}\n      stdout.println(`Total: ${price * quantity}`)\n\n      //Multiple expressions\n      stdout.println(`${name} is ${age} years old`)","migrationContext":"JavaScript: `Hello ${name}`. Kotlin: \"Hello $name\". Python: f\"Hello {name}\". EK9: `Hello ${name}` — same as JavaScript.","keywords":["backtick","embed","format","interpolation","string","template"],"primaryTopics":["string interpolation","backtick strings"],"typicalErrors":[{"error":"E11068","correct":"stdout.println(`Name: ${name}, Age: ${age}`)","incorrect":"stdout.println(\"Name is \" + name + \" today\")","explanation":"EK9 forbids 3+ part string concatenation with '+'; use backtick interpolation instead. See ek9 -h E11068 for details."}],"companions":[]}
{"id":1165,"category":"Operators and Expressions","question":"Split a comma-separated string into a list of fields.","url":"https://ek9.io/qa/QA1165.html","alternatePhrasings":["Write code to split a CSV line by commas into separate fields","I have a comma-delimited string and need each field as a list element","Given \"Alice,Bob,Charlie\", produce a list [\"Alice\", \"Bob\", \"Charlie\"]","In Java I'd use str.split(\",\"). Write the EK9 equivalent"],"answer":"Use the split method with a RegEx parameter:\n  fields <- line.split(/,/)\n\nThe split() method takes a RegEx and returns a List of String. You can then stream the result with cat or access individual elements. See Q33 for regular expressions. See Q175 for string transform patterns.","ek9Example":"defines module qa.operators.splitstring\n\n  defines program\n\n    SplitStringDemo()\n      stdout <- Stdout()\n\n      // Split a CSV line by comma\n      csvLine <- \"Alice,Bob,Charlie,Diana\"\n      fields <- csvLine.split(/,/)\n      stdout.println(`Fields: ${fields}`)\n\n      // Stream the split results\n      stdout.println(\"Individual fields:\")\n      cat fields > stdout\n\n      // Split with more complex regex (whitespace)\n      sentence <- \"Hello   World   EK9\"\n      words <- sentence.split(/ +/)\n      stdout.println(`Words: ${words}`)\n\n      // Split and count\n      fieldCount <- length fields\n      stdout.println(`Number of fields: ${fieldCount}`)","migrationContext":"Java: str.split(\",\") returns String[]. Python: str.split(\",\") returns list. Rust: str.split(',').collect(). Go: strings.Split(str, \",\"). EK9: str.split(/,/) takes RegEx, returns List of String.","keywords":["comma","csv","delimiter","fields","list","parse","regex","split","string"],"primaryTopics":["string split","CSV parsing","regex split"],"typicalErrors":[{"error":"E50060","correct":"      fields <- csvLine.split(/,/)","incorrect":"      fields <- csvLine.split(\",\")","explanation":"split() takes a RegEx (delimited by '/'), not a String - use /,/ not \",\". See ek9 -h E50060 for details."}],"companions":[]}
{"id":1166,"category":"Operators and Expressions","question":"Extract the first and last characters from a string.","url":"https://ek9.io/qa/QA1166.html","alternatePhrasings":["Write code to get the prefix and suffix characters of a string","I have a string and need its first character and last character separately","Given a name string, extract the initial and final characters using operators","In Java I'd use charAt(0) and charAt(length-1). Write the EK9 equivalent"],"answer":"Use the prefix (#<) and suffix (#>) operators:\n  firstChar <- #< name\n  lastChar <- #> name\n\nThe #< operator returns the first character (as a Character) and #> returns the last character. These are introspection operators available on String. See Q242 for conversion and introspection operators. See Q238 for the complete operator set.","ek9Example":"defines module qa.operators.substringextract\n\n  defines program\n\n    SubstringExtractDemo()\n      stdout <- Stdout()\n\n      name <- \"Hello\"\n\n      // Extract first and last characters\n      firstChar <- #< name\n      lastChar <- #> name\n      stdout.println(`First: ${firstChar}`)\n      stdout.println(`Last: ${lastChar}`)\n\n      // Use with different strings\n      words <- [\"EK9\", \"World\", \"A\"]\n      for word in words\n        first <- #< word\n        last <- #> word\n        stdout.println(`'${word}' -> first: ${first}, last: ${last}`)\n\n      // Length for context\n      size <- length name\n      stdout.println(`Length of '${name}': ${size}`)","migrationContext":"Java: str.charAt(0) and str.charAt(str.length()-1). Python: str[0] and str[-1]. Rust: str.chars().next() and str.chars().last(). Go: str[0] and str[len(str)-1]. EK9: #< str and #> str — prefix and suffix operators.","keywords":["character","extract","first","introspection","last","operator","prefix","string","suffix"],"primaryTopics":["prefix operator","suffix operator","character extraction"],"typicalErrors":[{"error":"E50060","correct":"firstChar <- #< name","incorrect":"firstChar <- name.charAt(0)","explanation":"String has no charAt() method. Use the #< prefix operator to get the first character and #> suffix operator for the last character. See ek9 -h E50060 for details."}],"companions":[]}
{"id":1167,"category":"Functions and Methods","question":"Write a function that returns a set or unset String depending on input.","url":"https://ek9.io/qa/QA1167.html","alternatePhrasings":["I need a function that sometimes returns a value and sometimes returns nothing","In Java I'd return Optional<String>. Write the EK9 function with optional return","Given a lookup function, return the found value or leave the return unset","Create a function whose return can be checked with ? by the caller"],"answer":"String() creates an unset return; caller guards with ?:\n  <- colour as String: String()\n  if name == \"sky\"\n    colour: \"blue\"\n\nIf the condition doesn't match, the return stays unset. Caller uses if-guard: if found <- findColour(\"sky\"). See Q1146, Q1087.","ek9Example":"defines module qa.functionsandmethods.functionoptionalreturn\n\n  defines function\n\n    findColour() as pure\n      -> name as String\n      <- colour as String: String()\n      if name == \"sky\"\n        colour: \"blue\"\n      else if name == \"grass\"\n        colour: \"green\"\n\n  defines program\n\n    FunctionOptionalReturnDemo()\n      stdout <- Stdout()\n\n      //Found — guard executes block\n      if skyColour <- findColour(\"sky\")\n        stdout.println(`Sky: ${skyColour}`)\n\n      //Not found — guard skips block\n      if oceanColour <- findColour(\"ocean\")\n        stdout.println(`Ocean: ${oceanColour}`)\n      else\n        stdout.println(\"Ocean colour not found\")","migrationContext":"Java: Optional<String>. Kotlin: String?. Rust: Option<String>. EK9: return String() (unset) or a value — caller uses ? or guard.","keywords":["function","guard","lookup","optional","return","unset"],"primaryTopics":["optional return","function with unset return"],"typicalErrors":[{"error":"E08180","correct":"      <- colour as String: String()","incorrect":"      <- colour as Optional of String","explanation":"EK9 doesn't use Optional for function returns. Return an unset value (String()) and let the caller guard with ?."}],"companions":[]}
{"id":1168,"category":"Functions and Methods","question":"Write a pure validation function that checks if an age is between 0 and 150.","url":"https://ek9.io/qa/QA1168.html","alternatePhrasings":["Write code to validate a numeric range using a pure function","I have an age value and need to verify it falls within a valid range","Given an integer, produce a Boolean indicating whether it is a valid age","In Java I'd write a static boolean isValidAge(int age). Write the EK9 equivalent"],"answer":"Define a pure function with a Boolean return:\n  isValidAge() as pure\n    -> age as Integer\n    <- rtn as Boolean: age >= 0 and age <= 150\n\nPure functions cannot modify parameters or access external state. They are ideal for validation logic because they are testable, composable, and safe to use in stream pipelines. See Q596 for function vs method. See Q568 for pure basics.","ek9Example":"defines module qa.functionsandmethods.validation\n\n  defines function\n\n    isValidAge() as pure\n      -> age as Integer\n      <- rtn as Boolean?\n      maxAge <- 150\n      rtn: age >= 0 and age <= maxAge\n\n    isValidPercentage() as pure\n      -> pct as Integer\n      <- rtn as Boolean?\n      maxPercent <- 100\n      rtn: pct >= 0 and pct <= maxPercent\n\n    isNonBlank() as pure\n      -> text as String\n      <- rtn as Boolean: text? and text.length() > 0\n\n  defines program\n\n    ValidationDemo()\n      stdout <- Stdout()\n\n      // Validate ages\n      ages <- [25, -1, 0, 150, 151, 42]\n      for age in ages\n        if isValidAge(age)\n          stdout.println(`Age ${age}: valid`)\n        else\n          stdout.println(`Age ${age}: invalid`)\n\n      // Validate percentages\n      scores <- [0, 50, 100, 101, -5]\n      for score in scores\n        if isValidPercentage(score)\n          stdout.println(`Score ${score}: valid`)\n        else\n          stdout.println(`Score ${score}: invalid`)\n\n      // Validate strings\n      names <- [\"Alice\", \"\", \"Bob\"]\n      for name in names\n        if isNonBlank(name)\n          stdout.println(`Name '${name}': valid`)\n        else\n          stdout.println(`Name '${name}': blank`)","migrationContext":"Java: static boolean isValidAge(int age) { return age >= 0 && age <= 150; }. Python: def is_valid_age(age): return 0 <= age <= 150. Rust: fn is_valid_age(age: i32) -> bool. EK9: isValidAge() as pure -> age as Integer <- rtn as Boolean.","keywords":["age","boolean","check","function","predicate","pure","range","validation"],"primaryTopics":["pure validation function","range check","predicate function"],"typicalErrors":[{"error":"E01072","correct":"rtn: age >= 0 and age <= maxAge","incorrect":"return age >= 0 and age <= maxAge","explanation":"EK9 has no return statement; assign the result to the declared return variable instead. See ek9 -h E01072 for details."}],"companions":[]}
{"id":1169,"category":"Functions and Methods","question":"Apply two transformations in sequence: double a number then add ten.","url":"https://ek9.io/qa/QA1169.html","alternatePhrasings":["Write code to chain two map stages in a stream pipeline","I have a list of integers and need to apply two sequential transformations","Given [1, 2, 3], double each value then add ten to each result","In Java I'd use stream().map(x -> x * 2).map(x -> x + 10). Write the EK9 equivalent"],"answer":"Chain map stages with separate pure functions:\n  cat items | map with doubleIt | map with addTen | collect as List of Integer\n\nEach map stage applies a pure function that transforms one value to another. Chaining multiple map stages composes transformations naturally through the pipeline. See Q235 for stream operations. See Q596 for function basics.","ek9Example":"defines module qa.functionsandmethods.composefunctions\n\n  defines function\n\n    doubleIt() as pure\n      -> num as Integer\n      <- rtn as Integer: num * 2\n\n    addTen() as pure\n      -> num as Integer\n      <- rtn as Integer: num + 10\n\n    intToString() as pure\n      -> num as Integer\n      <- rtn as String: $num\n\n  defines program\n\n    ComposeFunctionsDemo()\n      stdout <- Stdout()\n\n      items <- [1, 2, 3, 4, 5]\n\n      // Chain two transformations: double then add ten\n      transformed <- cat items | map with doubleIt | map with addTen | collect as List of Integer\n      stdout.println(`Doubled then plus ten: ${transformed}`)\n\n      // Stream to stdout\n      stdout.println(\"Streamed:\")\n      cat items | map with doubleIt | map with addTen | map with intToString > stdout","migrationContext":"Java: stream().map(x -> x * 2).map(x -> x + 10). Python: map(lambda x: x + 10, map(lambda x: x * 2, items)). Rust: iter().map(|x| x * 2).map(|x| x + 10). EK9: cat | map with fn1 | map with fn2.","keywords":["chain","compose","function","map","pipeline","sequence","stream","transform"],"primaryTopics":["function composition","chained map","stream transformation"],"typicalErrors":[{"error":"E50060","correct":"      transformed <- cat items | map with doubleIt | map with addTen | collect as List of Integer","incorrect":"      transformed <- items.stream().map(doubleIt).map(addTen).collect()","explanation":"EK9 streams use 'cat ... | map with fn | collect as ...' pipe syntax, not Java-style '.stream().map().collect()' method chains. See ek9 -h E50060 for details."}],"companions":[]}
{"id":1170,"category":"Enumerations","question":"Use an enumeration value in a switch statement to select behaviour.","url":"https://ek9.io/qa/QA1170.html","alternatePhrasings":["I need to switch on an enum value and handle each case","In Java I'd use switch on an enum. Write the EK9 enum switch pattern","Given a Priority enum, map each value to a label using switch","Dispatch on enumeration values using switch with case"],"answer":"Qualify enum values with the type name in switch cases:\n  switch priority\n    case Priority.HIGH\n      label: \"Urgent\"\n    case Priority.MEDIUM\n      label: \"Normal\"\n\nNo fallthrough -- each case is independent. See Q1151.","ek9Example":"defines module qa.enumerations.enumguard\n\n  defines type\n\n    Priority\n      HIGH\n      MEDIUM\n      LOW\n\n  defines program\n\n    EnumGuardDemo()\n      stdout <- Stdout()\n\n      priorities <- [Priority.HIGH, Priority.MEDIUM, Priority.LOW]\n\n      for priority in priorities\n        label <- \"Unknown\"\n        switch priority\n          case Priority.HIGH\n            label: \"Urgent\"\n          case Priority.MEDIUM\n            label: \"Normal\"\n          case Priority.LOW\n            label: \"Can wait\"\n          default\n            label: \"Unknown\"\n        stdout.println(`${priority}: ${label}`)","migrationContext":"Java: switch(priority) { case HIGH: ... }. Kotlin: when(priority) { HIGH -> ... }. EK9: switch priority / case Priority.HIGH.","keywords":["case","dispatch","enum","enumeration","switch","value"],"primaryTopics":["enum switch","enumeration dispatch"],"typicalErrors":[{"error":"E01010","correct":"      case Priority.HIGH","incorrect":"      case HIGH:","explanation":"EK9 enums must be qualified with the type name: Priority.HIGH, not just HIGH."}],"companions":[]}
{"id":1171,"category":"Sealed Types and Traits","question":"Define a sealed Shape class that permits only Circle, Square, and Triangle.","url":"https://ek9.io/qa/QA1171.html","alternatePhrasings":["Restrict which classes can extend Shape using 'allow only'","I need a closed hierarchy where only specific subclasses are allowed","In Kotlin I'd use a sealed class. Write the EK9 sealed class equivalent","Given a Shape base, lock it down so no other subclass can be added"],"answer":"Seal with 'allow only' and list permitted subtypes:\n  Shape allow only Circle, Square, Triangle as open\n    area()\n      <- rtn as String: \"Shape\"\n\n  Circle extends Shape\n    override area()\n      <- rtn as String: \"Circle\"\n\nOnly the listed types can extend Shape. The compiler rejects any other subclass.\n\nSee Q298 for sealed traits. See Q1121 for dispatching on sealed types.","ek9Example":"defines module qa.sealedtraits.sealedclassbasic\n\n  defines class\n\n    Shape allow only Circle, Square, Triangle as open\n      area()\n        <- rtn as String: \"Shape\"\n\n    Circle extends Shape\n      override area()\n        <- rtn as String: \"Circle\"\n\n    Square extends Shape\n      override area()\n        <- rtn as String: \"Square\"\n\n    Triangle extends Shape\n      override area()\n        <- rtn as String: \"Triangle\"\n\n  defines program\n\n    SealedClassBasicDemo()\n      stdout <- Stdout()\n\n      circle <- Circle()\n      square <- Square()\n      triangle <- Triangle()\n\n      stdout.println(circle.area())\n      stdout.println(square.area())\n      stdout.println(triangle.area())","migrationContext":"Kotlin: sealed class Shape. Java 17: sealed class Shape permits Circle, Square. Rust: enum Shape { Circle, Square }. EK9: Shape allow only Circle, Square as open.","keywords":["allow only","class","closed","hierarchy","restricted","sealed"],"primaryTopics":["sealed class","allow only"],"typicalErrors":[{"error":"E05240","correct":"    Shape allow only Circle, Square, Triangle as open","incorrect":"    Shape allow only Circle, Square as open","explanation":"A sealed class listing 'allow only Circle, Square' forbids Triangle from extending it — every permitted subtype must appear in the allow-only list. See ek9 -h E05240 for details."}],"companions":[]}
{"id":1172,"category":"Sealed Types and Traits","question":"Build a chained sealed hierarchy: Vehicle permits Car and Truck, Car permits Sedan and Hatchback.","url":"https://ek9.io/qa/QA1172.html","alternatePhrasings":["Create a multi-level sealed class tree with transitive allow only","I need Vehicle sealed to Car/Truck, and Car itself sealed to Sedan/Hatchback","In Java 17 I'd chain sealed permits. Write the EK9 multi-level sealed hierarchy","Given a two-level hierarchy, seal both levels with their own allow only lists"],"answer":"Chain 'allow only' at each level:\n  Vehicle allow only Car, Truck, Sedan, Hatchback as open\n  Car extends Vehicle allow only Sedan, Hatchback as open\n  Sedan extends Car\n  Truck extends Vehicle\n\nEach sealed class lists ALL permitted descendants in its own 'allow only'. The compiler enforces at both levels.\n\nSee Q1171 for basic sealed class. See Q303 for chained sealing.","ek9Example":"defines module qa.sealedtraits.sealedclasshierarchy\n\n  defines class\n\n    Vehicle allow only Car, Truck, Sedan, Hatchback as open\n      describe()\n        <- rtn as String: \"Vehicle\"\n\n    Car extends Vehicle allow only Sedan, Hatchback as open\n      override describe()\n        <- rtn as String: \"Car\"\n\n    Sedan extends Car\n      override describe()\n        <- rtn as String: \"Sedan\"\n\n    Hatchback extends Car\n      override describe()\n        <- rtn as String: \"Hatchback\"\n\n    Truck extends Vehicle\n      override describe()\n        <- rtn as String: \"Truck\"\n\n  defines program\n\n    SealedClassHierarchyDemo()\n      stdout <- Stdout()\n\n      sedan <- Sedan()\n      hatchback <- Hatchback()\n      truck <- Truck()\n\n      stdout.println(sedan.describe())\n      stdout.println(hatchback.describe())\n      stdout.println(truck.describe())","migrationContext":"Java 17: sealed class Vehicle permits Car, Truck; sealed class Car extends Vehicle permits Sedan. Kotlin: nested sealed classes. EK9: chained 'allow only' at each level.","keywords":["allow only","chained","hierarchy","multi-level","sealed","transitive"],"primaryTopics":["chained sealed classes","multi-level sealed hierarchy"],"typicalErrors":[{"error":"E05240","correct":"    Vehicle allow only Car, Truck, Sedan, Hatchback as open","incorrect":"    Vehicle allow only Car, Truck as open","explanation":"The top-level sealed class must list ALL descendants, including indirect ones (Sedan, Hatchback) — not just direct children."}],"companions":[]}
{"id":1173,"category":"Date, Time, and Duration","question":"Increment a date and calculate whether two dates differ.","url":"https://ek9.io/qa/QA1173.html","alternatePhrasings":["I need to advance a date by one day using the ++ operator","In Java I'd use LocalDate.plusDays(1). Write the EK9 date increment","Given a date, step forward one day and compare with another date","Move a date forward using ++ and check equality with =="],"answer":"++ advances Date by one day, <=> compares:\n  today++\n  cmp <- today <=> targetDate\n\n-- goes backward. DateTime increments by one second instead. See Q1103.","ek9Example":"defines module qa.datetimeduration.datearithmetic\n\n  defines program\n\n    DateArithmeticDemo()\n      stdout <- Stdout()\n\n      today <- 2026-04-07\n      stdout.println(`Today: ${today}`)\n\n      //Increment by one day\n      today++\n      stdout.println(`Tomorrow: ${today}`)\n\n      //Compare dates\n      targetDate <- 2026-04-09\n      cmp <- today <=> targetDate\n      stdout.println(`Comparison: ${cmp}`)","migrationContext":"Java: date.plusDays(1). Python: date + timedelta(days=1). Rust: date + Duration::days(1). EK9: date++ (one day forward).","keywords":["++","arithmetic","compare","date","day","increment"],"primaryTopics":["date increment","date comparison"],"typicalErrors":[{"error":"E50060","correct":"      today++","incorrect":"      today.plusDays(1)","explanation":"EK9 Date has no plusDays() method — use ++ to advance a Date by one day. See ek9 -h E50060 for details."}],"companions":[]}
{"id":1174,"category":"Date, Time, and Duration","question":"Calculate the duration between two dates.","url":"https://ek9.io/qa/QA1174.html","alternatePhrasings":["Write code to find the time span between a start and end date","I have two dates and need the duration between them","Given a start date and end date, compute how much time has elapsed","In Java I'd use ChronoUnit.DAYS.between(). Write the EK9 equivalent"],"answer":"Subtract one date from another using the - operator:\n  dur <- endDate - startDate\n  stdout.println(dur)\n\nDate subtraction produces a Duration. Use component accessors like .days(), .hours() to extract parts. Negative durations occur when subtracting a later date from an earlier one. See Q539 for date difference details. See Q540 for adding time.","ek9Example":"defines module qa.datetime.durationbetween\n\n  defines program\n\n    DurationBetweenDemo()\n      stdout <- Stdout()\n\n      // Subtract two dates to get a Duration\n      startDate <- 2024-01-01\n      endDate <- 2024-12-31\n      dur <- endDate - startDate\n      stdout.println(`Duration: ${dur}`)\n      stdout.println(`Days: ${dur.days()}`)\n\n      // Time subtraction\n      startTime <- 09:00\n      endTime <- 17:30\n      workDay <- endTime - startTime\n      stdout.println(`Work day: ${workDay}`)\n      stdout.println(`Hours: ${workDay.hours()}`)\n\n      // Duration literal comparison\n      oneWeek <- P7D\n      twoWeeks <- P14D\n      stdout.println(`One week < two weeks: ${oneWeek < twoWeeks}`)\n\n      // Negative duration\n      backwards <- startDate - endDate\n      stdout.println(`Backwards: ${backwards}`)","migrationContext":"Java: ChronoUnit.DAYS.between(d1, d2) or Period.between(). Python: (date2 - date1).days. Rust: chrono signed_duration_since(). Go: t2.Sub(t1). EK9: endDate - startDate gives Duration directly.","keywords":["between","date","days","difference","duration","elapsed","subtract","time span"],"primaryTopics":["duration between dates","date subtraction","time span"],"typicalErrors":[{"error":"E50060","correct":"dur <- endDate - startDate","incorrect":"dur <- endDate.daysBetween(startDate)","explanation":"Date has no daysBetween() method. Use the - operator to subtract dates and get a Duration, then call .days() for the day count. See ek9 -h E50060 for details."}],"companions":[]}
{"id":1175,"category":"Classes and OOP","question":"Define a Product record with name, price, and a constructor.","url":"https://ek9.io/qa/QA1175.html","alternatePhrasings":["Write code to create a simple data record in EK9","I need a lightweight data type with public fields for name and price","Given name and price fields, define a record type with default operators","In Java I'd use record Product(String name, double price). Write the EK9 equivalent"],"answer":"Define a record with public fields, a constructor, and default operator:\n  defines record\n    Product\n      name as String: String()\n      price as Float: 0.0\n      Product()\n        -> ...\n      default operator\n\nRecord fields are always public (accessed directly with product.name). Records cannot have methods — only constructors and operators. Use 'default operator' to auto-generate standard operators. See Q97 for class vs record. See Q1060 for record field visibility.","ek9Example":"defines module qa.classesandoop.definerecord\n\n  defines record\n\n    Product\n      name as String: String()\n      price as Float: 0.0\n\n      Product()\n        ->\n          name as String\n          price as Float\n        this.name: name\n        this.price: price\n\n      default operator\n\n  defines program\n\n    DefineRecordDemo()\n      stdout <- Stdout()\n\n      // Create a product using the constructor\n      widget <- Product(\"Widget\", 19.99)\n\n      // Access fields directly (public)\n      stdout.println(`Name: ${widget.name}`)\n      stdout.println(`Price: ${widget.price}`)\n\n      // Default operators provide $, ==, <>, <=> etc.\n      stdout.println(`Product: ${widget}`)\n\n      // Create another and compare\n      gadget <- Product(\"Gadget\", 29.99)\n      stdout.println(`Equal: ${widget == gadget}`)\n\n      // Records in a list\n      products <- [widget, gadget, Product(\"Gizmo\", 9.99)]\n      for product in products\n        stdout.println(`${product.name}: \\$${product.price}`)","migrationContext":"Java: record Product(String name, double price) — immutable, auto accessors. Python: @dataclass class Product. Rust: struct Product { name: String, price: f64 }. Kotlin: data class Product(val name: String, val price: Double). EK9: defines record with public fields, mutable by default, 'as pure' controls mutability.","keywords":["constructor","data","default operator","define","fields","product","public","record"],"primaryTopics":["define record","record constructor","data type"],"typicalErrors":[{"error":"E07290","correct":"default operator","incorrect":"getName()\n        <- rtn as String: name","explanation":"Records can only have constructors and operators, not methods. Adding a method like getName() triggers E07290. Access record fields directly since they are public. See ek9 -h E07290 for details."},{"error":"E50060","correct":"product.name","incorrect":"product.name()","explanation":"Record fields are public and accessed directly without parentheses. Using product.name() tries to call a method, but records do not have accessor methods. See ek9 -h E06180 for details."}],"companions":[]}
{"id":1176,"category":"Sealed Types and Traits","question":"Implement a Describable trait with a name method in a concrete Animal class.","url":"https://ek9.io/qa/QA1176.html","alternatePhrasings":["Write code to define a trait and implement it in a class with a constructor","I need a class that fulfils a Describable contract with name and describe methods","In Java I'd implement an interface on a class. Write the EK9 trait implementation","Given a Describable trait with describe(), create a Dog class that provides the implementation"],"answer":"Implement a trait with 'with trait of' and override abstract methods:\n\n  Dog with trait of Describable\n    override describe()\n      <- rtn as String: `Dog: ${name}`\n\nUse 'override' on every abstract trait method. The trait can have a default method that the class inherits without overriding.\n\nSee Q106 for trait basics. See Q108 for implementing traits. See Q1129 for trait delegation.","ek9Example":"defines module qa.sealedtraits.traitwithclass\n\n  defines trait\n\n    Describable\n      describe() as abstract\n        <- rtn as String?\n\n      summary() as pure\n        <- rtn as String: \"No summary available\"\n\n  defines class\n\n    Dog with trait of Describable\n      name <- String()\n\n      Dog()\n        -> name as String\n        this.name: name\n\n      override describe()\n        <- rtn as String: `Dog: ${name}`\n\n      override operator ? as pure\n        <- rtn as Boolean: name?\n\n      default operator\n\n  defines program\n\n    TraitWithClassDemo()\n      stdout <- Stdout()\n\n      dog <- Dog(\"Rex\")\n      stdout.println(dog.describe())\n      stdout.println(dog.summary())\n      stdout.println(`IsSet: ${dog?}`)","migrationContext":"Java: class Dog implements Describable. C#: class Dog : IDescribable. Python: class Dog(Describable). Kotlin: class Dog : Describable. EK9: Dog with trait of Describable, override methods.","keywords":["abstract","class","describe","implement","override","trait","with trait of"],"primaryTopics":["trait implementation","with trait of"],"typicalErrors":[{"error":"E05120","correct":"      override describe()\n        <- rtn as String: `Dog: ${name}`","incorrect":"      describe()\n        <- rtn as String: `Dog: ${name}`","explanation":"When implementing a trait's abstract method, the 'override' keyword is mandatory. Omitting it triggers E05120."}],"companions":[],"oracleToolHint":{"tool":"ek9_implement","intent":"trait","description":"Oracle can generate a class implementing a trait with all required method stubs."}}
{"id":1177,"category":"Enumerations","question":"Use fully qualified enum values in a switch to map Direction to its opposite.","url":"https://ek9.io/qa/QA1177.html","alternatePhrasings":["Write code to switch on an enum and produce the reverse direction","I need a function that returns the opposite of a compass Direction enum value","In Java I'd use switch(dir) { case NORTH: ... }. Write the EK9 qualified enum switch","Given North/South/East/West, create a function that maps each to its opposite using switch"],"answer":"Qualify enum values with the type name in switch cases:\n  switch dir\n    case Direction.North\n      rtn: Direction.South\n    case Direction.South\n      rtn: Direction.North\n\nAlways use Direction.North, never bare North. EK9 requires full qualification to prevent ambiguity.\n\nSee Q1170 for enum switch basics. See Q219 for enum features.","ek9Example":"defines module qa.enumerations.enumqualifiedvalues\n\n  defines type\n\n    Direction\n      North\n      South\n      East\n      West\n\n  defines function\n\n    opposite()\n      -> dir as Direction\n      <- rtn as Direction: Direction.North\n\n      switch dir\n        case Direction.North\n          rtn: Direction.South\n        case Direction.South\n          rtn: Direction.North\n        case Direction.East\n          rtn: Direction.West\n        case Direction.West\n          rtn: Direction.East\n        default\n          rtn: Direction.North\n\n  defines program\n\n    EnumQualifiedValuesDemo()\n      stdout <- Stdout()\n\n      directions <- [Direction.North, Direction.South, Direction.East, Direction.West]\n\n      for dir in directions\n        result <- opposite(dir)\n        stdout.println(`${dir} -> ${result}`)","migrationContext":"Java: case NORTH: (unqualified in switch). Kotlin: Direction.NORTH (qualified). Python: Direction.NORTH. EK9: Direction.North (always qualified).","keywords":["Direction","case","enum","opposite","qualified","switch"],"primaryTopics":["enum qualification","enum switch"],"typicalErrors":[{"error":"E50001","correct":"        case Direction.North","incorrect":"        case North","explanation":"EK9 enums must be qualified with the type name. Use Direction.North, not just North."}],"companions":[]}
{"id":1178,"category":"Collections and Data Structures","question":"Create a Dict, add entries with DictEntry, and look up values with getOrDefault.","url":"https://ek9.io/qa/QA1178.html","alternatePhrasings":["Write code to build a dictionary and retrieve values safely","I need a key-value map with add, lookup, and size operations in EK9","In Python I'd use dict[key] = value and dict.get(key, default). Write the EK9 Dict equivalent","Given a mapping of product names to prices, show Dict creation, adding, and lookup"],"answer":"Use Dict() of (K, V) with DictEntry and getOrDefault:\n\n  prices <- Dict() of (String, Float)\n  prices += DictEntry(\"Widget\", 9.99)\n  result <- prices.getOrDefault(\"Widget\", 0.0)\n\nDict uses parenthesised type parameters. Add entries with DictEntry(key, value). Retrieve with getOrDefault(key, fallback) — there is no .get() method.\n\nSee Q1161 for comprehensive Dict. See Q1158 for Dict iteration. See Q129 for missing keys.","ek9Example":"defines module qa.collections.dictcorrectsyntax\n\n  defines program\n\n    DictCorrectSyntaxDemo()\n      stdout <- Stdout()\n\n      //Create a Dict with parenthesised type parameters\n      prices <- Dict() of (String, Float)\n\n      //Add entries using DictEntry(key, value)\n      prices += DictEntry(\"Widget\", 9.99)\n      prices += DictEntry(\"Gadget\", 24.50)\n      prices += DictEntry(\"Gizmo\", 4.99)\n\n      //Look up existing key — returns the value\n      widgetPrice <- prices.getOrDefault(\"Widget\", 0.0)\n      stdout.println(`Widget: ${widgetPrice}`)\n\n      //Look up missing key — returns the default\n      missingPrice <- prices.getOrDefault(\"Unknown\", 0.0)\n      stdout.println(`Unknown: ${missingPrice}`)\n\n      //Check size\n      stdout.println(`Size: ${length prices}`)\n\n      //Iterate over all entries\n      for entry in prices\n        stdout.println($entry)\n\n      //Check if a key exists via getOrDefault\n      fallback <- 0.0\n      found <- prices.getOrDefault(\"Gadget\", fallback)\n      if found <> fallback\n        stdout.println(`Gadget found: ${found}`)","migrationContext":"Python: dict[k] = v, dict.get(k, default). Java: map.put(k, v), map.getOrDefault(k, default). Go: m[k] = v, if val, ok := m[k]. EK9: += DictEntry(k, v), getOrDefault(k, fallback).","keywords":["Dict","DictEntry","add","getOrDefault","key-value","lookup","map"],"primaryTopics":["Dict creation","Dict lookup"],"typicalErrors":[{"error":"E50060","correct":"prices.getOrDefault(\"Widget\", 0.0)","incorrect":"prices.get(\"Widget\")","explanation":"Dict has no get() method, so calling prices.get(\"Widget\") is unresolved — use getOrDefault(key, defaultValue) instead. See ek9 -h E50060 for details."}],"companions":[]}
{"id":1179,"category":"Collections and Data Structures","question":"Add entries to a Dict using DictEntry -- there is no curly-brace literal syntax.","url":"https://ek9.io/qa/QA1179.html","alternatePhrasings":["Write code showing that EK9 dicts cannot use {key: value} literals","I keep getting errors trying to initialise a dict with curly braces in EK9","In JavaScript I'd use {name: 'Alice', age: 30}. What is the EK9 Dict equivalent?","Build a settings Dict the correct way using DictEntry instead of literal syntax"],"answer":"EK9 has NO dict literal syntax. Build dicts with DictEntry:\n\n  settings <- Dict() of (String, String)\n  settings += DictEntry(\"theme\", \"dark\")\n  settings += DictEntry(\"lang\", \"en\")\n\nDo NOT try {\"theme\": \"dark\"} — it will not parse. Every entry uses DictEntry(key, value) added with +=.\n\nSee Q1178 for Dict creation. See Q1161 for full Dict operations.","ek9Example":"defines module qa.collections.dictnoliteralsyntax\n\n  defines program\n\n    DictNoLiteralSyntaxDemo()\n      stdout <- Stdout()\n\n      //WRONG: EK9 has no curly-brace dict literals\n      //settings <- {\"theme\": \"dark\", \"lang\": \"en\"}  <- PARSE ERROR\n\n      //RIGHT: Create Dict and add entries with DictEntry\n      settings <- Dict() of (String, String)\n      settings += DictEntry(\"theme\", \"dark\")\n      settings += DictEntry(\"lang\", \"en\")\n      settings += DictEntry(\"fontSize\", \"14\")\n\n      //Retrieve values\n      theme <- settings.getOrDefault(\"theme\", \"light\")\n      stdout.println(`Theme: ${theme}`)\n\n      lang <- settings.getOrDefault(\"lang\", \"en\")\n      stdout.println(`Language: ${lang}`)\n\n      //Iterate\n      for entry in settings\n        stdout.println($entry)","migrationContext":"JavaScript: {key: value}. Python: {key: value}. Java: Map.of(k, v). Go: map[string]string{k: v}. EK9: NO literal syntax — use Dict() of (K, V) and += DictEntry(k, v).","keywords":["Dict","DictEntry","curly brace","literal","no literal","syntax"],"primaryTopics":["no dict literal","DictEntry syntax"],"typicalErrors":[{"error":"E50050","correct":"      settings += DictEntry(\"theme\", \"dark\")","incorrect":"      settings <- {\"theme\": \"dark\"}","explanation":"EK9 has no curly-brace dict literal. Use Dict() of (K, V) and add entries with += DictEntry(k, v)."}],"companions":[]}
{"id":1180,"category":"Classes and OOP","question":"Define an abstract Shape class with an area method, and a concrete Circle subclass.","url":"https://ek9.io/qa/QA1180.html","alternatePhrasings":["Write code for an abstract class hierarchy with Shape and Circle","I need an abstract Shape with area() and a concrete Circle that provides the implementation","In Java I'd use abstract class Shape with abstract double area(). Write the EK9 version","Create a Shape/Circle hierarchy showing abstract methods, override, and default operator placement"],"answer":"Abstract classes use 'as abstract' and concrete children override:\n\n  Shape as abstract\n    area() as abstract\n      <- rtn as Float?\n    default operator ?\n\n  Circle extends Shape\n    override area()\n      <- rtn as Float: ...\n    override operator ? as pure\n      ...\n    default operator\n\nDo NOT use 'default operator' on the abstract parent -- use only 'default operator ?' because abstract types cannot generate all operators. Concrete children use 'default operator' LAST.\n\nSee Q103 for abstract classes. See Q116 for default operator.","ek9Example":"defines module qa.classesandoop.abstractclassoperators\n\n  defines class\n\n    Shape as abstract\n      area() as abstract\n        <- rtn as Float?\n\n      default operator ?\n\n    Circle extends Shape\n      radius <- 0.0\n\n      Circle()\n        -> radius as Float\n        this.radius: radius\n\n      override area()\n        <- rtn as Float: Float()\n        pi <- 3.14159\n        rtn: radius * radius * pi\n\n      override operator ? as pure\n        <- rtn as Boolean: radius?\n\n      operator $ as pure\n        <- rtn as String: `Circle(radius=${radius})`\n\n  defines program\n\n    AbstractClassOperatorsDemo()\n      stdout <- Stdout()\n\n      circle <- Circle(5.0)\n      stdout.println(`Area: ${circle.area()}`)\n      stdout.println(`IsSet: ${circle?}`)\n\n      //Polymorphism with abstract type\n      shapes <- List() of Shape\n      shapes += Circle(3.0)\n      shapes += Circle(7.5)\n\n      for shape in shapes\n        stdout.println(`Shape area: ${shape.area()}`)","migrationContext":"Java: abstract class Shape { abstract double area(); }. Python: class Shape(ABC): @abstractmethod. Rust: trait Shape { fn area(&self) -> f64; }. EK9: Shape as abstract with area() as abstract.","keywords":["abstract","area","circle","class","default operator","override","shape"],"primaryTopics":["abstract class","concrete subclass"],"typicalErrors":[{"error":"E07130","correct":"      override area()\n        <- rtn as Float: Float()","incorrect":"      //missing override of area()","explanation":"Concrete subclasses must implement all abstract methods. Omitting the override triggers E07130."}],"companions":[]}
{"id":1181,"category":"Advanced Type System","question":"Create a dynamic function that captures a greeting and prepends it to any input.","url":"https://ek9.io/qa/QA1181.html","alternatePhrasings":["Write code for a closure that captures a local variable as state","I need a dynamic function extending an abstract function type with named capture","In JavaScript I'd use a closure over a variable. Write the EK9 dynamic function equivalent","Build a Greeter dynamic function that captures a greeting string and prepends it to input"],"answer":"Define an abstract function type, then extend it dynamically:\n\n  Greeter as abstract\n    -> name as String\n    <- message as String?\n\n  greet <- (greeting: greetingText) extends Greeter as function\n    message: `${greeting} ${name}`\n\nUse 'as abstract' on the function definition. Dynamic functions use 'extends TypeName as function'. Named capture (greeting: greetingText) copies greetingText into field 'greeting'.\n\nSee Q1125 for dynamic function capture. See Q1126 for dynamic classes.","ek9Example":"defines module qa.advancedtypes.dynamicfunctioncorrect\n\n  defines function\n\n    Greeter as abstract\n      -> name as String\n      <- message as String?\n\n  defines program\n\n    DynamicFunctionCorrectDemo()\n      stdout <- Stdout()\n\n      greetingText <- \"Hello\"\n\n      //Create dynamic function with named capture\n      greet <- (greeting: greetingText) extends Greeter as function\n        message: `${greeting} ${name}`\n\n      stdout.println(greet(\"Alice\"))\n      stdout.println(greet(\"Bob\"))\n\n      //Another dynamic function with different captured state\n      farewellText <- \"Goodbye\"\n      farewell <- (farewell: farewellText) extends Greeter as function\n        message: `${farewell} ${name}`\n\n      stdout.println(farewell(\"Charlie\"))","migrationContext":"JavaScript: const greet = (name) => greeting + name (closure). Java: Function<String,String> greet = name -> greeting + name. Python: lambda name: greeting + name. EK9: (field: source) extends AbstractType as function.","keywords":["abstract","capture","closure","dynamic","extends","function","greeting"],"primaryTopics":["dynamic function syntax","abstract function"],"typicalErrors":[],"companions":[]}
{"id":1182,"category":"Generics","question":"Define a generic Pair class with two type parameters and proper field initialisation.","url":"https://ek9.io/qa/QA1182.html","alternatePhrasings":["Write code for a generic Pair with typed fields and both required constructors","I need a generic container holding two values of different types with default operator","In Java I'd use Pair<A,B>. Write the EK9 generic Pair with field initialisation","Create a Pair of type (A, B) with declaration-assigns for fields and override operator ?"],"answer":"Generic classes need two constructors and declaration-assigns for fields:\n\n  Pair of type (A, B)\n    first as A: A()\n    second as B: B()\n    default Pair()\n    Pair()\n      -> first as A, second as B\n\nFields use 'as T: T()' for initialisation. The default (no-arg) constructor is required for type inference. Override operator ? BEFORE default operator.\n\nSee Q1071 for two-constructor requirement. See Q194 for generic basics.","ek9Example":"defines module qa.genericsdeep.genericclassfields\n\n  defines class\n\n    Pair of type (A, B)\n      first as A: A()\n      second as B: B()\n\n      //Constructor 1: default (required for generic type inference)\n      default Pair()\n\n      //Constructor 2: parameterised (used by code)\n      Pair()\n        ->\n          first as A\n          second as B\n        this.first :=: first\n        this.second :=: second\n\n      first() as pure\n        <- rtn as A: first\n\n      second() as pure\n        <- rtn as B: second\n\n      override operator ? as pure\n        <- rtn as Boolean: first? and second?\n\n      operator $ as pure\n        <- rtn as String: `Pair(${$first}, ${$second})`\n\n  defines program\n\n    GenericClassFieldsDemo()\n      stdout <- Stdout()\n\n      nameAge <- Pair(\"Alice\", 30)\n      stdout.println(`First: ${nameAge.first()}`)\n      stdout.println(`Second: ${nameAge.second()}`)\n      stdout.println(`Pair: ${nameAge}`)\n\n      coords <- Pair(10.5, 20.3)\n      stdout.println(`Coords: ${coords}`)","migrationContext":"Java: class Pair<A,B> { A first; B second; }. Kotlin: data class Pair<A,B>(val first: A, val second: B). Rust: struct Pair<A,B> { first: A, second: B }. EK9: Pair of type (A, B) with two constructors.","keywords":["Pair","constructor","default","field","generic","two constructors","type parameter"],"primaryTopics":["generic class","field initialisation"],"typicalErrors":[{"error":"E06040","correct":"      default Pair()\n\n      //Constructor 2: parameterised (used by code)\n      Pair()","incorrect":"      Pair()\n        -> first as A, second as B","explanation":"Generic types require exactly two constructors: a default (no-arg) and a parameterised one. Missing the default triggers E06040."}],"companions":[]}
{"id":1183,"category":"Dependency Injection","question":"Register a concrete Logger as an abstract Logger type for dependency injection.","url":"https://ek9.io/qa/QA1183.html","alternatePhrasings":["Write code to wire a concrete component to its abstract interface for DI","I need to register ConsoleLogger as Logger and inject Logger into a program","In Spring I'd define @Component ConsoleLogger implements Logger. Write the EK9 DI equivalent","Set up application registration of a concrete type as its abstract parent for injection"],"answer":"Register concrete as abstract, inject with the abstract type:\n\n  register ConsoleLogger() as Logger\n  ...\n  logger as Logger!\n\nThe '!' suffix marks an injection point. Injection fields MUST use the abstract type (Logger!), not the concrete type (ConsoleLogger!). The compiler validates a matching registration exists.\n\nSee Q1110 for DI basics. See Q227 for compile-time validation. See Q228 for ordering.","ek9Example":"defines module qa.di.abstractrequired\n\n  defines component\n\n    Logger as abstract\n      log() as abstract\n        -> message as String\n        <- output as String?\n\n      default operator ?\n\n    ConsoleLogger is Logger\n      override log()\n        -> message as String\n        <- output as String: `[INFO] ${message}`\n\n      default operator ?\n\n  defines application\n\n    LoggingApp\n      register ConsoleLogger() as Logger\n\n  defines program\n\n    DiAbstractRequiredDemo() with application of LoggingApp\n      stdout <- Stdout()\n\n      //Inject with the ABSTRACT type, not the concrete type\n      logger as Logger!\n\n      result <- logger.log(\"Service started\")\n      stdout.println(result)\n\n      result2 <- logger.log(\"Processing request\")\n      stdout.println(result2)","migrationContext":"Spring: @Autowired Logger logger (interface). Guice: bind(Logger.class).to(ConsoleLogger.class). .NET: services.AddSingleton<ILogger, ConsoleLogger>(). EK9: register ConcreteType() as AbstractType, inject with AbstractType!.","keywords":["DI","abstract","application","component","concrete","inject","register"],"primaryTopics":["abstract injection","register as abstract"],"typicalErrors":[{"error":"E08210","correct":"      register ConsoleLogger() as Logger","incorrect":"      //no registration","explanation":"Every injection point (!) must have a matching registration. The compiler rejects missing registrations with E08210."}],"companions":[]}
{"id":1184,"category":"Operators and Expressions","question":"Extract magic literals in comparisons into named constants to satisfy the compiler.","url":"https://ek9.io/qa/QA1184.html","alternatePhrasings":["Write code that avoids magic numbers in comparisons by using named variables","I keep getting E11064 when comparing against literal values in EK9","In Java I'd use static final for magic numbers. What is the EK9 pattern for named constants?","Fix a comparison that uses a bare literal by extracting it into a named constant"],"answer":"Name all literals used in comparisons:\n\n  expensiveThreshold <- 50.0\n  if price > expensiveThreshold\n    stdout.println(\"Expensive\")\n\nEK9 rejects 'price > 50.0' with E11064 — magic literals in comparisons must be named. This improves readability and makes intent explicit.\n\nSee Q238 for operator set. See Q240 for arithmetic operators.","ek9Example":"defines module qa.operators.namedconstantsrequired\n\n  defines function\n\n    isExpensive()\n      -> price as Float\n      <- rtn as Boolean: false\n\n      //Named constant — compiler accepts this\n      expensiveThreshold <- 50.0\n      rtn: price > expensiveThreshold\n\n    isLongString()\n      -> item as String\n      <- rtn as Boolean: false\n\n      //Named constant for string length comparison\n      minLength <- 5\n      rtn: length item > minLength\n\n  defines program\n\n    NamedConstantsRequiredDemo()\n      stdout <- Stdout()\n\n      //Use named constants in comparisons\n      price <- 75.0\n      stdout.println(`Expensive: ${isExpensive(price)}`)\n\n      cheapPrice <- 10.0\n      stdout.println(`Expensive: ${isExpensive(cheapPrice)}`)\n\n      longWord <- \"International\"\n      stdout.println(`Long: ${isLongString(longWord)}`)\n\n      shortWord <- \"Hi\"\n      stdout.println(`Long: ${isLongString(shortWord)}`)","migrationContext":"Java: private static final double THRESHOLD = 50.0. Python: THRESHOLD = 50.0 (convention). Go: const threshold = 50.0. EK9: threshold <- 50.0 (compiler-enforced naming).","keywords":["E11064","comparison","constant","literal","magic","named","threshold"],"primaryTopics":["named constants","magic literal elimination"],"typicalErrors":[{"error":"E11064","correct":"      expensiveThreshold <- 50.0\n      rtn: price > expensiveThreshold","incorrect":"      if price > 50.0","explanation":"EK9 rejects magic literals in comparisons. Extract 50.0 into a named variable like expensiveThreshold."}],"companions":[]}
{"id":1185,"category":"Advanced Type System","question":"Define a Percentage type constrained to values between 0 and 100.","url":"https://ek9.io/qa/QA1185.html","alternatePhrasings":["Write code to create a constrained numeric type that rejects out-of-range values","I need a type that only accepts integers from 0 to 100 for percentage values","In Rust I'd use a newtype with validation. Write the EK9 constrained type equivalent","Build a Percentage constrained type and show how invalid values become unset"],"answer":"Use 'constrain as' with range checks:\n\n  defines type\n    Percentage as Integer constrain as\n      >= 0 and <= 100\n\nA bare constructor ASSERTS validity: Percentage(150) Panics at runtime on an out-of-range value, and a violating literal constant is the compile error E08260. For untrusted input use the fallible factory Percentage().of(value), which returns an UNSET object on violation (never Panics) — check with '?' after construction. Constrained types are disconnected from their base type — Percentage is NOT an Integer subtype.\n\nSee Q257 for constrained types. See Q1074 for constrained in record. See Q720 for constrainable types.","ek9Example":"defines module qa.advancedtypes.constrainedtype\n\n  defines type\n\n    Percentage as Integer constrain as\n      >= 0 and <= 100\n\n  defines record\n\n    ExamScore\n      studentName as String: String()\n      score as Percentage: Percentage(0)\n\n      ExamScore()\n        ->\n          studentName as String\n          score as Percentage\n        this.studentName :=: studentName\n        this.score :=: score\n\n      default operator\n\n  defines program\n\n    ConstrainedTypeDemo()\n      stdout <- Stdout()\n\n      //Valid — within range\n      passing <- Percentage(85)\n      stdout.println(`Valid score set: ${passing?}`)\n      if passing?\n        stdout.println(`Score: ${passing}`)\n\n      //Invalid — above 100: the fallible factory returns unset (a bare Percentage(150) would Panic at\n      //runtime, and a violating literal is the compile error E08260)\n      tooHigh <- Percentage().of(150)\n      stdout.println(`Too high set: ${tooHigh?}`)\n\n      //Invalid — below 0: of() returns unset\n      negative <- Percentage().of(-5)\n      stdout.println(`Negative set: ${negative?}`)\n\n      //Boundary values\n      zero <- Percentage(0)\n      hundred <- Percentage(100)\n      stdout.println(`Zero set: ${zero?}`)\n      stdout.println(`Hundred set: ${hundred?}`)\n\n      //Use in a record\n      result <- ExamScore(\"Alice\", Percentage(92))\n      stdout.println(`${result}`)","migrationContext":"Java: no built-in, uses Bean Validation @Min/@Max. Python: no type-level constraints. Rust: newtype with manual validation. Kotlin: value class with init check. EK9: 'constrain as' creates a validated, disconnected type.","keywords":["constrain as","constrained","disconnected","percentage","range","type","validation"],"primaryTopics":["constrained type","range constraint"],"typicalErrors":[{"error":"E50060","correct":"ExamScore(\"Alice\", Percentage(92))","incorrect":"ExamScore(\"Alice\", 92)","explanation":"Constrained types are disconnected from their base type, so passing the Integer 92 where a Percentage is expected fails to resolve the ExamScore constructor — wrap it as Percentage(92). See ek9 -h E50060 for details."}],"companions":[]}
{"id":1186,"category":"Operators and Expressions","question":"How do I compare two dates and determine which is earlier?","url":"https://ek9.io/qa/QA1186.html","alternatePhrasings":["Compare two Date values and find which comes first in EK9.","I need to check whether a project start date is before the end date.","In Java I used isBefore() on LocalDate — what is the EK9 equivalent?"],"answer":"EK9 Date values support all comparison operators directly. Use < > <= >= == <> for Boolean checks and <=> for Integer ordering.\n\nDATE LITERALS\nWrite dates as YYYY-MM-DD:\n  startDate <- 2024-01-15\n  endDate <- 2024-06-30\n\nCOMPARISON\n  if startDate < endDate          earlier date\n  if startDate == endDate         same date\n  ordering <- startDate <=> endDate  negative = earlier, zero = same, positive = later\n\nCOALESCING\n  earlier <- startDate <? endDate   returns the earlier date\n  later <- startDate >? endDate     returns the later date\n\nNo method calls like isBefore() or compareTo(). Operators work the same on Date as on Integer, String, or any other comparable type.\n\nSee Q542 for full temporal comparison. See Q238 for the complete operator set. See Q539 for date differences.","ek9Example":"defines module qa.operators.comparedates\n\n  defines function\n\n    <?- Compare two dates and return spaceship ordering. -?>\n    compareDates() as pure\n      ->\n        left as Date\n        right as Date\n      <- rtn as Integer: left <=> right\n\n    <?- Check if the first date is before the second. -?>\n    isBefore() as pure\n      ->\n        left as Date\n        right as Date\n      <- rtn as Boolean: left < right\n\n    <?- Return the earlier of two dates using coalescing min. -?>\n    earlierOf() as pure\n      ->\n        left as Date\n        right as Date\n      <- rtn as Date: left <? right\n\n    <?- Return the later of two dates using coalescing max. -?>\n    laterOf() as pure\n      ->\n        left as Date\n        right as Date\n      <- rtn as Date: left >? right\n\n  defines program\n\n    CompareDatesDemo()\n      stdout <- Stdout()\n\n      // === DATE LITERALS ===\n\n      startDate <- 2024-01-15\n      endDate <- 2024-06-30\n      midYear <- 2024-06-30\n\n      // === COMPARISON VIA FUNCTION ===\n\n      before <- isBefore(startDate, endDate)\n      stdout.println(`Start before end: ${before}`)\n\n      // === SPACESHIP ORDERING VIA FUNCTION ===\n\n      ordering <- compareDates(startDate, endDate)\n      stdout.println(`Start <=> End: ${ordering}`)\n\n      sameCheck <- compareDates(endDate, midYear)\n      stdout.println(`End <=> MidYear: ${sameCheck}`)\n\n      // === COALESCING: FIND EARLIER AND LATER ===\n\n      earlier <- earlierOf(startDate, endDate)\n      later <- laterOf(startDate, endDate)\n      stdout.println(`Earlier: ${earlier}`)\n      stdout.println(`Later: ${later}`)\n\n      // === SORTED DATE LIST VIA STREAM ===\n\n      dates <- List() of Date\n      dates += endDate\n      dates += startDate\n      dates += midYear\n\n      sorted <- cat dates | sort | collect as List of Date\n      stdout.println(`Sorted dates: ${sorted}`)\n\n      // === STRING CONVERSION ===\n\n      dateStr <- $startDate\n      stdout.println(`Start date as string: ${dateStr}`)\n\n      // === ISSET CHECK ===\n\n      require startDate?\n      unsetDate <- Date()\n      require ~unsetDate?","migrationContext":"Java: LocalDate.isBefore(), compareTo(), isAfter() methods. Python: direct < > operators on datetime.date. Rust: PartialOrd on chrono::NaiveDate. Go: t.Before(), t.After() methods. Kotlin: compareTo() or operator overloading. EK9: direct < > <= >= == <> <=> operators on Date literals, no method calls needed.","keywords":["after","before","coalescing","compare","date","earlier","later","operator","ordering","spaceship","temporal"],"primaryTopics":["date comparison","date operators","temporal ordering"],"typicalErrors":[{"error":"E50060","correct":"<- rtn as Boolean: left < right","incorrect":"<- rtn as Boolean: left.isBefore(right)","explanation":"Date has no isBefore() method — EK9 uses comparison operators (left < right) directly on temporal types. See ek9 -h E50060 for details."}],"companions":[]}
{"id":1187,"category":"Operators and Expressions","question":"How do I check if a meeting time is before noon in EK9?","url":"https://ek9.io/qa/QA1187.html","alternatePhrasings":["Compare two Time values to see which is earlier.","I need to determine whether a scheduled time falls in the morning.","In Python I compared datetime.time objects — how does EK9 handle this?"],"answer":"EK9 Time values use the same comparison operators as every other type. Write time literals as HH:MM or HH:MM:SS.\n\nTIME LITERALS\n  meetingTime <- 09:30\n  noon <- 12:00\n  precise <- 14:30:45\n\nCOMPARISON\n  if meetingTime < noon          check if before noon\n  ordering <- meetingTime <=> noon  negative = earlier, zero = same, positive = later\n\nCOALESCING\n  earlierTime <- meetingTime <? noon  returns whichever is earlier\n\nAll comparison operators (< > <= >= == <> <=>) work identically on Time, Date, Integer, String, and any type that implements them.\n\nSee Q542 for full temporal comparison. See Q31 for Date and Time basics.","ek9Example":"defines module qa.operators.comparetimes\n\n  defines function\n\n    <?- Check if a time is before another time. -?>\n    isBeforeTime() as pure\n      ->\n        check as Time\n        against as Time\n      <- rtn as Boolean: check < against\n\n    <?- Compare two times and return spaceship ordering. -?>\n    compareTimes() as pure\n      ->\n        left as Time\n        right as Time\n      <- rtn as Integer: left <=> right\n\n    <?- Return the earlier of two times using coalescing min. -?>\n    earlierOf() as pure\n      ->\n        left as Time\n        right as Time\n      <- rtn as Time: left <? right\n\n    <?- Return the later of two times using coalescing max. -?>\n    laterOf() as pure\n      ->\n        left as Time\n        right as Time\n      <- rtn as Time: left >? right\n\n  defines program\n\n    CompareTimesDemo()\n      stdout <- Stdout()\n\n      // === TIME LITERALS ===\n\n      meetingTime <- 09:30\n      noon <- 12:00\n      afternoon <- 14:30:45\n\n      // === IS THE MEETING BEFORE NOON? ===\n\n      isMorning <- isBeforeTime(meetingTime, noon)\n      stdout.println(`Meeting is morning: ${isMorning}`)\n\n      isAfternoonLater <- isBeforeTime(noon, afternoon)\n      stdout.println(`Afternoon after noon: ${isAfternoonLater}`)\n\n      // === SPACESHIP ORDERING VIA FUNCTION ===\n\n      ordering <- compareTimes(meetingTime, noon)\n      stdout.println(`Meeting <=> noon: ${ordering}`)\n\n      // === COALESCING: FIND EARLIEST AND LATEST ===\n\n      earlierTime <- earlierOf(meetingTime, noon)\n      laterTime <- laterOf(meetingTime, noon)\n      stdout.println(`Earlier: ${earlierTime}`)\n      stdout.println(`Later: ${laterTime}`)\n\n      // === SORTED TIME LIST VIA STREAM ===\n\n      times <- List() of Time\n      times += afternoon\n      times += meetingTime\n      times += noon\n\n      sorted <- cat times | sort | collect as List of Time\n      stdout.println(`Sorted times: ${sorted}`)\n\n      // === STRING AND ISSET ===\n\n      timeStr <- $meetingTime\n      stdout.println(`Time string: ${timeStr}`)\n      require meetingTime?\n\n      unsetTime <- Time()\n      require ~unsetTime?","migrationContext":"Java: LocalTime.isBefore(), compareTo(). Python: direct operators on datetime.time. Rust: PartialOrd on chrono::NaiveTime. Go: no built-in time-of-day type, use time.Time methods. EK9: direct < > == operators on Time literals, consistent with all types.","keywords":["afternoon","before","clock","compare","morning","noon","operator","temporal","time"],"primaryTopics":["time comparison","time operators","temporal check"],"typicalErrors":[{"error":"E50060","correct":"      <- rtn as Boolean: check < against","incorrect":"      <- rtn as Boolean: check.isBefore(against)","explanation":"Time has no isBefore() method; compare Time values directly with the < operator. See ek9 -h E50060 for details."}],"companions":[]}
{"id":1188,"category":"Operators and Expressions","question":"How do I compare two durations and find the shorter one using coalescing?","url":"https://ek9.io/qa/QA1188.html","alternatePhrasings":["Compare Duration values and pick the lesser using <? in EK9.","I need to select the shorter of two time periods for a break schedule.","In Java I used Duration.compareTo — what does EK9 use instead?"],"answer":"EK9 Duration uses ISO 8601 literal syntax and supports all comparison and coalescing operators.\n\nDURATION LITERALS\n  shortBreak <- PT15M       15 minutes\n  longBreak <- PT1H         1 hour\n  halfDay <- PT4H30M        4 hours 30 minutes\n  threeDays <- P3D          3 days\n\nCOMPARISON\n  if shortBreak < longBreak     shorter duration\n  ordering <- shortBreak <=> longBreak\n\nCOALESCING\n  shorter <- shortBreak <? longBreak   returns the shorter duration\n  longer <- shortBreak >? longBreak    returns the longer duration\n\nThe <? operator handles unset values safely: if one side is unset, it returns whichever is valid.\n\nSee Q542 for temporal comparison. See Q243 for coalescing operators. See Q540 for duration arithmetic.","ek9Example":"defines module qa.operators.durationops\n\n  defines function\n\n    shorterOf() as pure\n      ->\n        left as Duration\n        right as Duration\n      <- rtn as Duration: left <? right\n\n    longerOf() as pure\n      ->\n        left as Duration\n        right as Duration\n      <- rtn as Duration: left >? right\n\n  defines program\n\n    DurationOpsDemo()\n      stdout <- Stdout()\n\n      // === DURATION LITERALS (ISO 8601) ===\n\n      shortBreak <- PT15M\n      longBreak <- PT1H\n      halfDay <- PT4H30M\n\n      // === COMPARISON ===\n\n      if shortBreak < longBreak\n        stdout.println(\"Short break is shorter than long break\")\n\n      require shortBreak < longBreak\n      require longBreak > shortBreak\n      require shortBreak <> longBreak\n\n      // === SPACESHIP ORDERING ===\n\n      ordering <- shortBreak <=> longBreak\n      stdout.println(`Short <=> Long: ${ordering}`)\n      require ordering < 0\n\n      // === COALESCING: FIND SHORTER AND LONGER ===\n\n      shorter <- shorterOf(shortBreak, longBreak)\n      longer <- longerOf(shortBreak, longBreak)\n      stdout.println(`Shorter: ${shorter}`)\n      stdout.println(`Longer: ${longer}`)\n      require shorter == shortBreak\n      require longer == longBreak\n\n      // === HALF DAY COMPARISON ===\n\n      require halfDay > longBreak\n      require halfDay < PT5H\n      stdout.println(`Half day: ${halfDay}`)\n\n      // === STRING AND ISSET ===\n\n      durStr <- $shortBreak\n      stdout.println(`Duration string: ${durStr}`)\n      require shortBreak?\n\n      unsetDuration <- Duration()\n      require ~unsetDuration?","migrationContext":"Java: Duration.compareTo(), no coalescing. Python: timedelta comparison with < >. Rust: Duration::cmp(). Go: time.Duration comparison with < >. EK9: ISO 8601 literals (PT15M, P3D), direct operators, <? coalescing for safe minimum selection.","keywords":["coalescing","compare","duration","iso8601","lesser","longer","operator","period","shorter","time"],"primaryTopics":["duration comparison","coalescing operators","duration literals"],"typicalErrors":[{"error":"E50060","correct":"left <? right","incorrect":"left.min(right)","explanation":"Duration has no min() method, so left.min(right) is unresolved — use the <? coalescing operator to select the lesser value. See ek9 -h E50060 for details."}],"companions":[]}
{"id":1189,"category":"Operators and Expressions","question":"How do I compare two money amounts and find the cheaper price in EK9?","url":"https://ek9.io/qa/QA1189.html","alternatePhrasings":["Compare two Money values and select the lesser amount.","I need to find the cheapest item from two prices using operators.","In Java I used BigDecimal.compareTo for money — what does EK9 use?"],"answer":"EK9 Money uses the same comparison and coalescing operators as every other type. Literals use amount#CURRENCY format.\n\nMONEY LITERALS\n  priceA <- 29.99#USD\n  priceB <- 45.50#USD\n\nCOMPARISON\n  if priceA < priceB          cheaper price\n  ordering <- priceA <=> priceB  negative = cheaper, zero = same, positive = more expensive\n\nCOALESCING\n  cheaper <- priceA <? priceB    returns the lower price\n  dearer <- priceA >? priceB     returns the higher price\n\nIMPORTANT: Comparison only works between same-currency Money values. Comparing 10#GBP with 20#USD returns unset — EK9 prevents accidental cross-currency comparison.\n\nSee Q35 for Money basics. See Q149 for currency safety. See Q243 for coalescing operators.","ek9Example":"defines module qa.operators.moneycompare\n\n  defines function\n\n    cheaperOf() as pure\n      ->\n        left as Money\n        right as Money\n      <- rtn as Money: left <? right\n\n    dearerOf() as pure\n      ->\n        left as Money\n        right as Money\n      <- rtn as Money: left >? right\n\n  defines program\n\n    MoneyCompareDemo()\n      stdout <- Stdout()\n\n      // === MONEY LITERALS ===\n\n      priceA <- 29.99#USD\n      priceB <- 45.50#USD\n      sameAsA <- 29.99#USD\n\n      // === COMPARISON ===\n\n      if priceA < priceB\n        stdout.println(\"Price A is cheaper\")\n\n      require priceA < priceB\n      require priceB > priceA\n      require priceA == sameAsA\n      require priceA <> priceB\n\n      // === SPACESHIP ORDERING ===\n\n      ordering <- priceA <=> priceB\n      stdout.println(`PriceA <=> PriceB: ${ordering}`)\n      require ordering < 0\n\n      // === COALESCING: FIND CHEAPER AND DEARER ===\n\n      cheaper <- cheaperOf(priceA, priceB)\n      dearer <- dearerOf(priceA, priceB)\n      stdout.println(`Cheaper: ${cheaper}`)\n      stdout.println(`Dearer: ${dearer}`)\n      require cheaper == priceA\n      require dearer == priceB\n\n      // === STRING CONVERSION ===\n\n      priceStr <- $priceA\n      stdout.println(`Price as string: ${priceStr}`)\n\n      // === ISSET ===\n\n      require priceA?\n      unsetMoney <- Money()\n      require ~unsetMoney?\n\n      // === COPY OPERATOR ===\n\n      backup <- 0.00#USD\n      backup :=: priceA\n      require backup == priceA\n      stdout.println(`Copied price: ${backup}`)","migrationContext":"Java: BigDecimal.compareTo(), no literal syntax, manual RoundingMode. Python: decimal.Decimal comparison. Rust: no built-in money type. Go: no built-in money type. EK9: 29.99#USD literals, direct < > == operators, <? coalescing, automatic HALF_UP rounding, cross-currency safety.","keywords":["cheaper","coalescing","compare","cost","currency","gbp","money","operator","price","usd"],"primaryTopics":["money comparison","price comparison","money operators"],"typicalErrors":[{"error":"E50001","correct":"cheaper <- cheaperOf(priceA, priceB)","incorrect":"cheaper <- Math.min(priceA, priceB)","explanation":"EK9 has no Math type, so Math.min will not resolve (E50001); use the <? coalescing operator to select the lesser value. See ek9 -h E50001 for details."}],"companions":[]}
{"id":1190,"category":"Operators and Expressions","question":"How do I check if two colours are equal and convert a colour to its string representation?","url":"https://ek9.io/qa/QA1190.html","alternatePhrasings":["Compare two Colour values for equality in EK9.","I need to verify whether a pixel matches a target colour.","In CSS I compare hex codes as strings — how does EK9 compare colours?"],"answer":"EK9 Colour uses hex literals (#RRGGBB) and supports equality, comparison, and string conversion operators.\n\nCOLOUR LITERALS\n  primary <- #FF0000       red\n  other <- #FF0000         same red\n  accent <- #00FF00        green\n\nEQUALITY\n  if primary == other          same colour\n  if primary <> accent         different colour\n\nSTRING CONVERSION\n  str <- $primary              converts to string representation\n\nCOMPARISON\n  ordering <- primary <=> accent  spaceship comparison\n  brighter <- primary >? accent   coalescing for greater\n\nColour also supports arithmetic (+ for blend, - for remove) and HSL manipulation (withHue, withSaturation, withLightness). Use 'ek9 -h Colour' for the full API.\n\nSee Q34 for Colour basics. See Q238 for the complete operator set.","ek9Example":"defines module qa.operators.colourequality\n\n  defines program\n\n    ColourEqualityDemo()\n      stdout <- Stdout()\n\n      // === COLOUR LITERALS ===\n\n      primary <- #FF0000\n      other <- #FF0000\n      accent <- #00FF00\n      highlight <- #0000FF\n\n      // === EQUALITY ===\n\n      if primary == other\n        stdout.println(\"Primary and other are the same colour\")\n\n      require primary == other\n      require primary <> accent\n      require accent <> highlight\n\n      // === SPACESHIP ORDERING ===\n\n      ordering <- primary <=> accent\n      stdout.println(`Red <=> Green: ${ordering}`)\n\n      // === STRING CONVERSION ===\n\n      redStr <- $primary\n      greenStr <- $accent\n      stdout.println(`Red as string: ${redStr}`)\n      stdout.println(`Green as string: ${greenStr}`)\n\n      // === COALESCING ===\n\n      lesser <- primary <? accent\n      greater <- primary >? accent\n      stdout.println(`Lesser colour: ${lesser}`)\n      stdout.println(`Greater colour: ${greater}`)\n\n      // === ISSET ===\n\n      require primary?\n      unsetColour <- Colour()\n      require ~unsetColour?\n\n      // === COPY ===\n\n      copied <- Colour()\n      copied :=: primary\n      require copied == primary\n      stdout.println(`Copied colour: ${copied}`)","migrationContext":"Java: java.awt.Color.equals(), getRGB() for comparison. Python: no built-in, tuple comparison. CSS: string comparison of hex codes. JavaScript: no built-in colour type. EK9: #RRGGBB literals, direct == <> operators, $ for string, arithmetic blending.","keywords":["color","colour","compare","conversion","equality","hex","match","operator","rgb","string"],"primaryTopics":["colour equality","colour comparison","colour string"],"typicalErrors":[{"error":"E50060","correct":"if primary == other","incorrect":"if primary.equals(other)","explanation":"Colour has no equals() method. EK9 uses the == operator for equality checks on all types. See ek9 -h E50060 for details."}],"companions":[]}
{"id":1191,"category":"Operators and Expressions","question":"How do I compare two dimensions and determine which is longer?","url":"https://ek9.io/qa/QA1191.html","alternatePhrasings":["Compare two Dimension values to find the larger measurement.","I need to check whether width exceeds height for a layout calculation.","In CSS I compare pixel values as numbers — how does EK9 compare dimensions?"],"answer":"EK9 Dimension uses unit-suffixed literals and supports comparison operators for same-unit values.\n\nDIMENSION LITERALS\n  width <- 2.5m\n  height <- 1.8m\n\nCOMPARISON (SAME UNIT)\n  if width > height          width is longer\n  ordering <- width <=> height  spaceship comparison\n  longer <- width >? height    coalescing for greater\n\nIMPORTANT: Comparison only works between same-unit Dimensions. Comparing 2.5m with 10px returns unset — EK9 prevents unit mismatch errors at runtime.\n\nUse convert() to change units before comparing across different unit systems.\n\nSee Q36 for Dimension basics. See Q238 for the complete operator set.","ek9Example":"defines module qa.operators.dimensioncompare\n\n  defines function\n\n    higherOf() as pure\n      ->\n        left as Dimension\n        right as Dimension\n      <- rtn as Dimension: left >? right\n\n    lowerOf() as pure\n      ->\n        left as Dimension\n        right as Dimension\n      <- rtn as Dimension: left <? right\n\n  defines program\n\n    DimensionCompareDemo()\n      stdout <- Stdout()\n\n      // === DIMENSION LITERALS ===\n\n      width <- 2.5m\n      height <- 1.8m\n      sameWidth <- 2.5m\n\n      // === COMPARISON ===\n\n      if width > height\n        stdout.println(\"Width is longer than height\")\n\n      require width > height\n      require height < width\n      require width == sameWidth\n      require width <> height\n\n      // === SPACESHIP ORDERING ===\n\n      ordering <- width <=> height\n      stdout.println(`Width <=> Height: ${ordering}`)\n      require ordering > 0\n\n      // === COALESCING: FIND LONGER AND SHORTER ===\n\n      longer <- higherOf(width, height)\n      shorter <- lowerOf(width, height)\n      stdout.println(`Longer: ${longer}`)\n      stdout.println(`Shorter: ${shorter}`)\n      require longer == width\n      require shorter == height\n\n      // === CROSS-UNIT RETURNS UNSET ===\n\n      pixels <- 100px\n      crossUnit <- width == pixels\n      require ~crossUnit?\n      stdout.println(`Cross-unit compare isSet: ${crossUnit?}`)\n\n      // === STRING AND ISSET ===\n\n      dimStr <- $width\n      stdout.println(`Width as string: ${dimStr}`)\n      require width?\n\n      unsetDim <- Dimension()\n      require ~unsetDim?","migrationContext":"Java: plain double, manual unit tracking, no compile-time safety. F#: units of measure at compile time. CSS: string comparison. Python: no built-in dimension type. EK9: unit-suffixed literals (2.5m, 10px), same-unit comparison, cross-unit returns unset for safety.","keywords":["compare","dimension","height","layout","longer","measurement","operator","shorter","unit","width"],"primaryTopics":["dimension comparison","measurement operators","unit comparison"],"typicalErrors":[{"error":"E50060","correct":"if width > height","incorrect":"if width.getValue() > height.getValue()","explanation":"Dimension has no getValue() method. EK9 uses comparison operators directly on Dimension values. Use #< to extract the numeric value if needed. See ek9 -h E50060 for details."}],"companions":[]}
{"id":1192,"category":"Operators and Expressions","question":"How do I increment a character and compare two characters in EK9?","url":"https://ek9.io/qa/QA1192.html","alternatePhrasings":["Increment a Character from 'a' to 'd' using ++ in EK9.","I need to iterate through characters by incrementing a letter variable.","In C I used char++ to walk through the alphabet — does EK9 support this?"],"answer":"EK9 Character supports increment (++), decrement (--), comparison, and all standard operators.\n\nCHARACTER LITERALS\n  letter <- 'a'\n\nINCREMENT\n  letter++        moves from 'a' to 'b'\n  letter++        moves from 'b' to 'c'\n  letter++        moves from 'c' to 'd'\n\nCOMPARISON\n  if letter == 'd'          exact match\n  if letter > 'a'           ordering (alphabetical)\n  ordering <- 'a' <=> 'z'  spaceship comparison\n\nSTRING CONVERSION\n  str <- $letter            character to string\n\nCharacter uses the same operators as Integer, Date, Money — the pattern is universal across all EK9 types.\n\nSee Q238 for the complete operator set. See Q23 for basic types.","ek9Example":"defines module qa.operators.characterops\n\n  defines function\n\n    <?- Compare two characters and return spaceship ordering. -?>\n    compareChars() as pure\n      ->\n        left as Character\n        right as Character\n      <- rtn as Integer: left <=> right\n\n    <?- Check if two characters are equal. -?>\n    charsEqual() as pure\n      ->\n        left as Character\n        right as Character\n      <- rtn as Boolean: left == right\n\n  defines program\n\n    CharacterOpsDemo()\n      stdout <- Stdout()\n\n      // === CHARACTER LITERAL ===\n\n      letter <- 'a'\n      stdout.println(`Starting letter: ${letter}`)\n\n      // === INCREMENT ===\n\n      letter++\n      stdout.println(`After first ++: ${letter}`)\n\n      letter++\n      stdout.println(`After second ++: ${letter}`)\n\n      letter++\n      stdout.println(`After third ++: ${letter}`)\n\n      // === COMPARISON VIA FUNCTION ===\n\n      targetLetter <- 'd'\n      matched <- charsEqual(letter, targetLetter)\n      stdout.println(`Letter equals target: ${matched}`)\n\n      // === SPACESHIP ORDERING VIA FUNCTION ===\n\n      ordering <- compareChars(letter, targetLetter)\n      stdout.println(`letter <=> target: ${ordering}`)\n\n      alphaOrder <- compareChars('a', 'z')\n      stdout.println(`'a' <=> 'z': ${alphaOrder}`)\n\n      // === DECREMENT ===\n\n      letter--\n      stdout.println(`After --: ${letter}`)\n\n      // === STRING CONVERSION ===\n\n      charStr <- $letter\n      stdout.println(`Character as string: ${charStr}`)\n\n      // === ISSET ===\n\n      require letter?\n      unsetChar <- Character()\n      require ~unsetChar?\n\n      // === SORTED CHARACTER LIST ===\n\n      chars <- List() of Character\n      chars += 'z'\n      chars += 'a'\n      chars += 'm'\n\n      sorted <- cat chars | sort | collect as List of Character\n      stdout.println(`Sorted chars: ${sorted}`)","migrationContext":"Java: char is a primitive integer type, char++ works. Python: no char type, ord()/chr() functions. Rust: char is Unicode scalar, no increment. Go: rune is int32, rune++ works. EK9: Character is a full type with ++, --, comparison, $, ? operators.","keywords":["alphabet","char","character","compare","decrement","increment","letter","operator","walk"],"primaryTopics":["character increment","character comparison","character operators"],"typicalErrors":[{"error":"E07620","correct":"letter++","incorrect":"letter = letter + 1","explanation":"Character does not support addition with Integer. Use the ++ operator to increment a Character to the next Unicode code point. See ek9 -h E01072 for details."}],"companions":[]}
{"id":1193,"category":"Operators and Expressions","question":"How do I compare screen resolutions and find the higher one in EK9?","url":"https://ek9.io/qa/QA1193.html","alternatePhrasings":["Compare two Resolution values and pick the greater using >? in EK9.","I need to select the higher DPI for print output from two options.","How do I work with Resolution literals like 96dpi and 300dpi?"],"answer":"EK9 Resolution uses DPI/PPI literals and supports all comparison and coalescing operators.\n\nRESOLUTION LITERALS\n  screenDpi <- 96dpi\n  printDpi <- 300dpi\n\nCOMPARISON\n  if printDpi > screenDpi       higher resolution\n  ordering <- screenDpi <=> printDpi  spaceship comparison\n\nCOALESCING\n  higherDpi <- screenDpi >? printDpi   returns the higher resolution\n  lowerDpi <- screenDpi <? printDpi    returns the lower resolution\n\nResolution works with the same operators as Integer, Date, Money, Dimension — EK9 operators are universal across types.\n\nSee Q23 for basic types. See Q238 for the complete operator set.","ek9Example":"defines module qa.operators.resolutioncompare\n\n  defines function\n\n    higherOf() as pure\n      ->\n        left as Resolution\n        right as Resolution\n      <- rtn as Resolution: left >? right\n\n    lowerOf() as pure\n      ->\n        left as Resolution\n        right as Resolution\n      <- rtn as Resolution: left <? right\n\n  defines program\n\n    ResolutionCompareDemo()\n      stdout <- Stdout()\n\n      // === RESOLUTION LITERALS ===\n\n      screenDpi <- 96dpi\n      printDpi <- 300dpi\n      retinaDisplay <- 220dpi\n\n      // === COMPARISON ===\n\n      if printDpi > screenDpi\n        stdout.println(\"Print has higher resolution than screen\")\n\n      require printDpi > screenDpi\n      require screenDpi < printDpi\n      require screenDpi <> printDpi\n\n      // === SPACESHIP ORDERING ===\n\n      ordering <- screenDpi <=> printDpi\n      stdout.println(`Screen <=> Print: ${ordering}`)\n      require ordering < 0\n\n      // === COALESCING: FIND HIGHER AND LOWER ===\n\n      higherDpi <- higherOf(screenDpi, printDpi)\n      lowerDpi <- lowerOf(screenDpi, printDpi)\n      stdout.println(`Higher DPI: ${higherDpi}`)\n      stdout.println(`Lower DPI: ${lowerDpi}`)\n      require higherDpi == printDpi\n      require lowerDpi == screenDpi\n\n      // === RETINA COMPARISON ===\n\n      require retinaDisplay > screenDpi\n      require retinaDisplay < printDpi\n      stdout.println(`Retina: ${retinaDisplay}`)\n\n      // === STRING AND ISSET ===\n\n      dpiStr <- $printDpi\n      stdout.println(`Print DPI as string: ${dpiStr}`)\n      require printDpi?\n\n      unsetRes <- Resolution()\n      require ~unsetRes?","migrationContext":"Java: no built-in resolution type, use int with manual DPI tracking. Python: no built-in. CSS: dpi in media queries but not programmable. Go: no built-in. EK9: 96dpi literals, direct comparison operators, >? coalescing for safe maximum selection.","keywords":["compare","display","dpi","higher","lower","operator","ppi","print","resolution","screen"],"primaryTopics":["resolution comparison","DPI comparison","resolution operators"],"typicalErrors":[{"error":"E50001","correct":"      higherDpi <- higherOf(screenDpi, printDpi)","incorrect":"      higherDpi <- Math.max(screenDpi, printDpi)","explanation":"There is no 'Math' type in EK9 so 'Math.max' does not resolve; use the '>?' coalescing operator to select the greater value. See ek9 -h E50001 for details."}],"companions":[]}
{"id":1194,"category":"Operators and Expressions","question":"How do I compare two timeout values in milliseconds in EK9?","url":"https://ek9.io/qa/QA1194.html","alternatePhrasings":["Compare Millisecond values to determine which timeout is shorter.","I need to select the shorter of two network timeouts.","In Java I compared long millisecond values — how does EK9 handle this?"],"answer":"EK9 Millisecond is a built-in type with literal syntax and full operator support.\n\nMILLISECOND LITERALS\n  shortTimeout <- 250ms\n  longTimeout <- 5000ms\n\nCOMPARISON\n  if shortTimeout < longTimeout   shorter timeout\n  ordering <- shortTimeout <=> longTimeout\n\nCOALESCING\n  quickest <- shortTimeout <? longTimeout  returns the shorter\n  slowest <- shortTimeout >? longTimeout   returns the longer\n\nMillisecond supports the same operator set as Duration, Date, Integer — EK9's type system is consistent.\n\nSee Q542 for temporal comparison. See Q243 for coalescing operators.","ek9Example":"defines module qa.operators.millisecondcompare\n\n  defines function\n\n    <?- Compare two millisecond values and return spaceship ordering. -?>\n    compareMs() as pure\n      ->\n        left as Millisecond\n        right as Millisecond\n      <- rtn as Integer: left <=> right\n\n    <?- Check if the first timeout is shorter than the second. -?>\n    isShorter() as pure\n      ->\n        left as Millisecond\n        right as Millisecond\n      <- rtn as Boolean: left < right\n\n    <?- Return the quicker of two timeouts using coalescing min. -?>\n    quickerOf() as pure\n      ->\n        left as Millisecond\n        right as Millisecond\n      <- rtn as Millisecond: left <? right\n\n    <?- Return the slower of two timeouts using coalescing max. -?>\n    slowerOf() as pure\n      ->\n        left as Millisecond\n        right as Millisecond\n      <- rtn as Millisecond: left >? right\n\n  defines program\n\n    MillisecondCompareDemo()\n      stdout <- Stdout()\n\n      // === MILLISECOND LITERALS ===\n\n      shortTimeout <- 250ms\n      longTimeout <- 5000ms\n      mediumTimeout <- 1500ms\n\n      // === COMPARISON VIA FUNCTION ===\n\n      shorter <- isShorter(shortTimeout, longTimeout)\n      stdout.println(`Short is faster: ${shorter}`)\n\n      // === SPACESHIP ORDERING VIA FUNCTION ===\n\n      ordering <- compareMs(shortTimeout, longTimeout)\n      stdout.println(`Short <=> Long: ${ordering}`)\n\n      mediumOrder <- compareMs(mediumTimeout, longTimeout)\n      stdout.println(`Medium <=> Long: ${mediumOrder}`)\n\n      // === COALESCING: FIND QUICKEST AND SLOWEST ===\n\n      quickest <- quickerOf(shortTimeout, longTimeout)\n      slowest <- slowerOf(shortTimeout, longTimeout)\n      stdout.println(`Quickest: ${quickest}`)\n      stdout.println(`Slowest: ${slowest}`)\n\n      // === SORTED TIMEOUT LIST VIA STREAM ===\n\n      timeouts <- List() of Millisecond\n      timeouts += longTimeout\n      timeouts += shortTimeout\n      timeouts += mediumTimeout\n\n      sorted <- cat timeouts | sort | collect as List of Millisecond\n      stdout.println(`Sorted timeouts: ${sorted}`)\n\n      // === STRING AND ISSET ===\n\n      msStr <- $shortTimeout\n      stdout.println(`Timeout as string: ${msStr}`)\n      require shortTimeout?\n\n      unsetMs <- Millisecond()\n      require ~unsetMs?\n\n      // === COPY OPERATOR ===\n\n      backup <- 0ms\n      backup :=: shortTimeout\n      stdout.println(`Copied timeout: ${backup}`)","migrationContext":"Java: plain long for millis, System.currentTimeMillis(), no type safety. Python: float seconds, no millis type. Rust: Duration::from_millis(). Go: time.Duration(n * time.Millisecond). EK9: 250ms literals, direct comparison, type-safe millisecond operations.","keywords":["compare","duration","latency","longer","millisecond","ms","network","operator","shorter","timeout"],"primaryTopics":["millisecond comparison","timeout comparison","millisecond operators"],"typicalErrors":[{"error":"E50060","correct":"left < right","incorrect":"left.toMillis() < right.toMillis()","explanation":"Millisecond values are already in milliseconds and support comparison operators directly — there is no 'toMillis()' method to resolve. See ek9 -h E50060 for details."}],"companions":[]}
{"id":1195,"category":"Operators and Expressions","question":"How do I define an Event record with Date, Time, and String fields and compare two events?","url":"https://ek9.io/qa/QA1195.html","alternatePhrasings":["Create a record with multiple built-in type fields and use default operators.","I need to build an Event record with temporal fields and compare instances.","In Java I implemented Comparable on a multi-field class — how does EK9 do this?"],"answer":"Define a record with typed fields, a constructor, and 'default operator' to get field-by-field comparison, equality, copy, isSet, and string conversion.\n\nRECORD DEFINITION\n  Event\n    eventDate as Date: Date()\n    eventTime as Time: Time()\n    title as String: String()\n    constructor, then 'default operator' LAST\n\nDEFAULT OPERATOR\n  'default operator' generates ==, <>, <, >, <=, >=, <=>, $, ?, :=:, #? and more from the fields. It must be the LAST thing in the record body.\n\nCOMPARISON\n  if eventA < eventB        field-by-field comparison\n  if eventA == eventB       field-by-field equality\n\nRecords have public fields — no getters needed. Since every field (Date, Time, String) supports operators, the default operator works seamlessly across all of them.\n\nSee Q116 for default operator. See Q245 for custom type operators. See Q238 for the complete operator set.","ek9Example":"defines module qa.operators.mixedtypeclass\n\n  defines record\n\n    Event\n      eventDate as Date: Date()\n      eventTime as Time: Time()\n      title as String: String()\n\n      Event() as pure\n        ->\n          theDate as Date\n          theTime as Time\n          theTitle as String\n        this.eventDate :=: theDate\n        this.eventTime :=: theTime\n        this.title :=: theTitle\n\n      default operator\n\n  defines program\n\n    MixedTypeClassDemo()\n      stdout <- Stdout()\n\n      // === CREATE EVENTS ===\n\n      eventA <- Event(2024-03-15, 09:00, \"Sprint Planning\")\n      eventB <- Event(2024-03-15, 14:00, \"Retrospective\")\n      eventC <- Event(2024-06-01, 10:00, \"Release Review\")\n\n      // === COMPARISON (field-by-field via default operator) ===\n\n      if eventA < eventB\n        stdout.println(\"Sprint Planning is before Retrospective\")\n\n      require eventA < eventB\n      require eventB < eventC\n      require eventA <> eventB\n\n      // === EQUALITY ===\n\n      sameAsA <- Event(2024-03-15, 09:00, \"Sprint Planning\")\n      require eventA == sameAsA\n      stdout.println(`Events equal: ${eventA == sameAsA}`)\n\n      // === SPACESHIP ORDERING ===\n\n      ordering <- eventA <=> eventC\n      stdout.println(`Planning <=> Release: ${ordering}`)\n      require ordering < 0\n\n      // === STRING CONVERSION ===\n\n      eventStr <- $eventA\n      stdout.println(`Event: ${eventStr}`)\n\n      // === ISSET ===\n\n      require eventA?\n      unsetEvent <- Event()\n      require ~unsetEvent?\n\n      // === COPY OPERATOR ===\n\n      copied <- Event()\n      copied :=: eventA\n      require copied == eventA\n      stdout.println(`Copied event: ${copied}`)\n\n      // === COALESCING ===\n\n      earlier <- eventA <? eventC\n      later <- eventA >? eventC\n      stdout.println(`Earlier event: ${earlier}`)\n      stdout.println(`Later event: ${later}`)\n      require earlier == eventA\n      require later == eventC","migrationContext":"Java: implement Comparable, override equals/hashCode/toString manually. Python: @dataclass with order=True. Rust: #[derive(Eq, Ord)]. Kotlin: data class. Go: manual comparison functions. EK9: 'default operator' auto-generates all operators from fields, one line replaces boilerplate.","keywords":["compare","constructor","date","default","event","fields","mixed","operator","record","string","time"],"primaryTopics":["multi-field record","default operator","record comparison"],"typicalErrors":[{"error":"E06820","correct":"      default operator\n\n  defines program","incorrect":"default operator\n      title as String: String()\n\n      (fields after default operator)","explanation":"The 'default operator' must be the LAST declaration in the record body. Placing fields or methods after it triggers E06820. See ek9 -h E06820 for details."}],"companions":[]}
{"id":1196,"category":"Control Flow","question":"How do I guard a date lookup so the block only runs if a date is found?","url":"https://ek9.io/qa/QA1196.html","alternatePhrasings":["Use an if guard with a Date return value in EK9.","I need to safely handle a function that might not return a valid deadline.","In Java I checked if the date was null — how does EK9 guard against unset dates?"],"answer":"EK9 guard variables combine declaration with an isSet check. If the function returns an unset Date, the if body is skipped.\n\nGUARD PATTERN\n  if deadline <- findDeadline()\n    stdout.println(deadline)      only runs if deadline is set\n  else\n    stdout.println(\"No deadline\")  runs if deadline is unset\n\nThe guard works identically for Date, Time, Money, String, or any type with the ? operator. One universal pattern replaces null checks across all types.\n\nFUNCTION RETURNING UNSET\n  findDeadline()\n    <- rtn <- Date()    Date() creates an unset Date\n\nSee Q74 for guard if. See Q75 for guard switch. See Q76 for guard for. See Q77 for guard while.","ek9Example":"defines module qa.flow.guard.withdate\n\n  defines function\n\n    findDeadline()\n      <- rtn <- Date()\n\n    findActiveDeadline()\n      <- rtn <- 2024-09-30\n\n    findProjectEnd()\n      <- rtn <- 2024-12-31\n\n  defines program\n\n    GuardWithDateDemo()\n      stdout <- Stdout()\n\n      // === GUARD WITH UNSET DATE ===\n\n      if deadline <- findDeadline()\n        stdout.println(`Deadline: ${deadline}`)\n      else\n        stdout.println(\"No deadline found\")\n\n      // === GUARD WITH SET DATE ===\n\n      if deadline <- findActiveDeadline()\n        stdout.println(`Active deadline: ${deadline}`)\n      else\n        stdout.println(\"No active deadline\")\n\n      // === GUARD WITH ADDITIONAL CONDITION ===\n\n      today <- 2024-06-15\n      if projectEnd <- findProjectEnd() with projectEnd > today\n        stdout.println(`Project ends in the future: ${projectEnd}`)\n\n      // === GUARD SCOPING ===\n      // The guard variable is scoped to the if/else block\n      // It does not leak into surrounding scope\n\n      if dueDate <- findActiveDeadline()\n        daysMessage <- `Due: ${dueDate}`\n        stdout.println(daysMessage)\n\n      // === MULTIPLE GUARDS IN SEQUENCE ===\n\n      if first <- findActiveDeadline()\n        if second <- findProjectEnd()\n          if first < second\n            stdout.println(`Active deadline is before project end`)","migrationContext":"Java: if (date != null) { use(date); } — null check separate from declaration. Python: if date := get_date() (walrus, 3.8+, None only). Rust: if let Some(d) = find_deadline(). Go: if d := findDeadline(); d != nil. Kotlin: val d = findDeadline(); if (d != null). EK9: if deadline <- findDeadline() — one expression for declare + check + scope.","keywords":["check","control","date","deadline","flow","guard","if","lookup","safe","temporal","unset"],"primaryTopics":["guard with date","temporal guard","date safety"],"typicalErrors":[{"error":"E01072","correct":"      if deadline <- findDeadline()\n        stdout.println(`Deadline: ${deadline}`)\n      else\n        stdout.println(\"No deadline found\")","incorrect":"deadline <- findDeadline()\n      if deadline == null\n        return\n      stdout.println(deadline)","explanation":"EK9 has no null and no return statement. Use guard variables in if statements to combine declaration and isSet checking in one expression. See ek9 -h E01072 for details."}],"companions":[]}
{"id":1197,"category":"Streams and Pipelines","question":"How do I sort a list of dates in ascending order using a stream pipeline?","url":"https://ek9.io/qa/QA1197.html","alternatePhrasings":["Sort Date values in a stream pipeline and collect the result.","I need to order a list of holidays chronologically using cat and sort.","In Java I used Stream.sorted() on LocalDate — what is the EK9 equivalent?"],"answer":"EK9 stream pipelines sort any type that supports the <=> operator. Since Date has built-in comparison, sort works directly.\n\nSORT DATES\n  dates <- [2024-12-25, 2024-01-01, 2024-07-04]\n  sorted <- cat dates | sort | collect as List of Date\n\nThe sort stage uses the type's <=> operator. No Comparator needed for built-in types.\n\nCOMBINED PIPELINE\n  cat dates | sort | head 2 | collect as List of Date   first two chronologically\n\nThis works identically for Date, Time, Integer, String, Money, Duration — any type with <=> is sortable in a stream.\n\nSee Q235 for stream operations reference. See Q120 for sort details. See Q542 for date comparison.","ek9Example":"defines module qa.streams.sortdates\n\n  defines program\n\n    StreamSortDatesDemo()\n      stdout <- Stdout()\n\n      // === DATE LIST ===\n\n      dates <- [2024-12-25, 2024-01-01, 2024-07-04, 2024-03-17, 2024-11-28]\n      stdout.println(`Original: ${dates}`)\n\n      // === SORT ASCENDING ===\n\n      sorted <- cat dates | sort | collect as List of Date\n      stdout.println(`Sorted: ${sorted}`)\n\n      // === FIRST TWO CHRONOLOGICALLY ===\n\n      firstTwo <- cat dates | sort | head 2 | collect as List of Date\n      stdout.println(`First two: ${firstTwo}`)\n\n      // === LAST TWO CHRONOLOGICALLY ===\n\n      lastTwo <- cat dates | sort | tail 2 | collect as List of Date\n      stdout.println(`Last two: ${lastTwo}`)\n\n      // === SORT AND DEDUPLICATE ===\n\n      datesWithDuplicates <- [2024-01-01, 2024-07-04, 2024-01-01, 2024-12-25, 2024-07-04]\n      unique <- cat datesWithDuplicates | sort | uniq | collect as List of Date\n      stdout.println(`Unique sorted: ${unique}`)\n\n      // === DIRECT OUTPUT ===\n\n      stdout.println(\"All dates sorted:\")\n      cat dates | sort > stdout","migrationContext":"Java: dates.stream().sorted().collect(Collectors.toList()). Python: sorted(dates). Rust: dates.sort(). Go: sort.Slice(dates, func). EK9: cat dates | sort | collect as List of Date — Unix pipe syntax, implicit Comparator from <=> operator.","keywords":["ascending","cat","chronological","collect","date","list","order","pipeline","sort","stream"],"primaryTopics":["stream sort","date sorting","stream pipeline"],"typicalErrors":[{"error":"E50060","correct":"sorted <- cat dates | sort | collect as List of Date","incorrect":"sorted <- dates.sort()","explanation":"List has no sort() method. EK9 uses stream pipelines for sorting: cat the list, pipe through sort, and collect the result. See ek9 -h E50030 for details."}],"companions":[]}
{"id":1198,"category":"Classes and OOP","question":"Calculate an end date from a start date and a duration gap on a Project class.","url":"https://ek9.io/qa/QA1198.html","alternatePhrasings":["Define a Project with a start date and duration, then compute the end date.","I need a class that holds a Date and Duration and can calculate when the project finishes.","In Java I'd use LocalDate.plus(Period) — how does EK9 combine Date and Duration?","Given a project start date and a duration, show the projected completion date."],"answer":"Define a class with Date and Duration fields, accessor methods, and a computed endDate() method. Use the + operator on Date to add Duration and get the end date.\n\nCLASS WITH DATE AND DURATION\n  Project\n    startDate <- Date()\n    gap <- Duration()\n    startDate() accessor for the start date field\n    gap() accessor for the duration field\n    endDate() computes startDate + gap\n\nDATE ARITHMETIC\n  Date supports operator + with Duration, returning a new Date:\n    endDate <- 2024-01-15 + P2M   gives a date 2 months later\n\nThis uses a class (not a record) because endDate() is a computed method. Records cannot have methods, only constructors and operators.\n\nSee Q1186 for date comparison. See Q1188 for duration operations. See Q1175 for record definition.","ek9Example":"defines module qa.classesandoop.projectenddate\n\n  defines class\n\n    Project\n      startDate <- Date()\n      gap <- Duration()\n\n      default private Project() as pure\n\n      Project() as pure\n        ->\n          theStart as Date\n          theGap as Duration\n        this.startDate :=: theStart\n        this.gap :=: theGap\n\n      startDate() as pure\n        <- rtn as Date: Date(startDate)\n\n      gap() as pure\n        <- rtn as Duration: Duration(gap)\n\n      endDate() as pure\n        <- rtn as Date: startDate + gap\n\n      default operator\n\n  defines program\n\n    ProjectEndDateDemo()\n      stdout <- Stdout()\n\n      // === PROJECT WITH 2-MONTH DURATION ===\n\n      sprintProject <- Project(2024-01-15, P2M)\n      stdout.println(`Sprint start: ${sprintProject.startDate()}`)\n      stdout.println(`Sprint end: ${sprintProject.endDate()}`)\n\n      // === PROJECT WITH 3-DAY DURATION ===\n\n      hotfix <- Project(2024-06-01, P3D)\n      stdout.println(`Hotfix start: ${hotfix.startDate()}`)\n      stdout.println(`Hotfix end: ${hotfix.endDate()}`)\n\n      // === PROJECT WITH 1-YEAR DURATION ===\n\n      roadmap <- Project(2024-03-15, P1Y)\n      stdout.println(`Roadmap start: ${roadmap.startDate()}`)\n      stdout.println(`Roadmap end: ${roadmap.endDate()}`)\n\n      // === COMPARE END DATES ===\n\n      if hotfix.endDate() < sprintProject.endDate()\n        stdout.println(\"Hotfix finishes before sprint project\")\n\n      if roadmap.endDate() > sprintProject.endDate()\n        stdout.println(\"Roadmap extends beyond sprint project\")","migrationContext":"Java: LocalDate start; Period duration; start.plus(duration). Python: date + timedelta. Rust: NaiveDate + Duration (chrono). Go: t.Add(duration). EK9: startDate + gap — operator + on Date with Duration, no method calls.","keywords":["add","arithmetic","class","constructor","date","duration","end date","operator plus","project"],"primaryTopics":["date duration arithmetic","class definition","operator +"],"typicalErrors":[{"error":"E07290","correct":"  defines class\n\n    Project\n      startDate <- Date()","incorrect":"defines record\n      Project\n        endDate()\n          <- rtn as Date: startDate + gap","explanation":"Records cannot have methods, only constructors and operators. Use a class when you need methods like endDate(). See ek9 -h E07290 for details."}],"companions":[]}
{"id":1199,"category":"Streams and Pipelines","question":"Sort a list of Meeting records by date then by time using a stream pipeline.","url":"https://ek9.io/qa/QA1199.html","alternatePhrasings":["Build a record with Date and Time fields and sort instances in a stream.","I need to order meetings chronologically using cat, sort, and collect.","In Java I'd use Comparator.comparing(Meeting::getDate).thenComparing(Meeting::getTime). What is the EK9 equivalent?","Given meetings on different dates and times, sort them into chronological order."],"answer":"Define a record with Date and Time fields, then use 'default operator' to get field-by-field comparison. Stream sort uses the <=> operator automatically.\n\nRECORD DEFINITION\n  Meeting with meetingDate, meetingTime, title fields\n  'default operator' generates <=> comparing fields in declaration order (date first, then time, then title)\n\nSTREAM SORT\n  sorted <- cat meetings | sort | collect as List of Meeting\n\nThe sort stage uses <=> from the default operator. Fields declared first have highest sort priority. Date before Time before title means chronological ordering happens naturally.\n\nSee Q1135 for record sort pipeline. See Q1195 for mixed-type record. See Q1197 for date sorting.","ek9Example":"defines module qa.streams.sortmeetingsbydatetime\n\n  defines record\n\n    Meeting\n      meetingDate as Date: Date()\n      meetingTime as Time: Time()\n      title as String: String()\n\n      Meeting() as pure\n        ->\n          theDate as Date\n          theTime as Time\n          theTitle as String\n        this.meetingDate :=: theDate\n        this.meetingTime :=: theTime\n        this.title :=: theTitle\n\n      default operator\n\n  defines program\n\n    SortMeetingsByDateTimeDemo()\n      stdout <- Stdout()\n\n      // === CREATE MEETINGS ===\n\n      meetings <- List() of Meeting\n      meetings += Meeting(2024-03-15, 14:00, \"Retrospective\")\n      meetings += Meeting(2024-03-15, 09:00, \"Sprint Planning\")\n      meetings += Meeting(2024-03-14, 16:00, \"Code Review\")\n      meetings += Meeting(2024-03-16, 10:30, \"Demo Day\")\n\n      // === SORT BY DATE THEN TIME (field declaration order) ===\n\n      sorted <- cat meetings | sort | collect as List of Meeting\n      stdout.println(\"Sorted meetings:\")\n      cat sorted > stdout\n\n      // === FIRST TWO MEETINGS ===\n\n      firstTwo <- cat meetings | sort | head 2 | collect as List of Meeting\n      stdout.println(\"First two meetings:\")\n      cat firstTwo > stdout\n\n      // === VERIFY ORDERING ===\n\n      earliest <- Meeting(2024-03-14, 16:00, \"Code Review\")\n      latest <- Meeting(2024-03-16, 10:30, \"Demo Day\")\n      require earliest < latest","migrationContext":"Java: stream().sorted(Comparator.comparing(m -> m.date).thenComparing(m -> m.time)). Python: sorted(meetings, key=lambda m: (m.date, m.time)). Rust: sort_by_key with tuple. Go: sort.Slice with custom less function. EK9: declare fields in sort priority order + default operator + cat | sort | collect.","keywords":["chronological","collect","date","default operator","meeting","pipeline","record","sort","stream","time"],"primaryTopics":["stream sort custom record","multi-field sort","date time ordering"],"typicalErrors":[{"error":"E50001","correct":"sorted <- cat meetings | sort | collect as List of Meeting","incorrect":"meetings.sort(Comparator.comparing(Meeting::getDate))","explanation":"EK9 uses stream pipelines for sorting, not method calls. Define <=> via default operator and use cat | sort | collect. See ek9 -h E50060 for details."}],"companions":[]}
{"id":1200,"category":"Operators and Expressions","question":"Sum the line totals of an order where each line has a quantity and unit price in Money.","url":"https://ek9.io/qa/QA1200.html","alternatePhrasings":["Calculate order totals by multiplying Money by Integer quantities.","I need to compute the total cost of an order with multiple line items.","In Java I'd use BigDecimal.multiply for each line — how does EK9 handle Money arithmetic?","Given line items with quantity and unit price, sum up the order total."],"answer":"EK9 Money supports operator * with Integer for quantity multiplication and operator + for accumulation.\n\nMONEY ARITHMETIC\n  unitPrice <- 29.99#USD\n  lineTotal <- unitPrice * quantity   Money * Integer = Money\n\nACCUMULATION\n  Use a for loop with += to sum Money values:\n  total <- 0.00#USD\n  for item in items\n    total += item.unitPrice * item.quantity\n\nMoney handles precision automatically with HALF_UP rounding. Same-currency operations work directly; cross-currency returns unset.\n\nSee Q1189 for money comparison. See Q35 for Money basics. See Q1139 for stream join.","ek9Example":"defines module qa.operators.moneyordertotal\n\n  defines record\n\n    OrderLine\n      description as String: String()\n      unitPrice as Money: Money()\n      quantity as Integer: Integer()\n\n      OrderLine() as pure\n        ->\n          theDescription as String\n          theUnitPrice as Money\n          theQuantity as Integer\n        this.description :=: theDescription\n        this.unitPrice :=: theUnitPrice\n        this.quantity :=: theQuantity\n\n      default operator\n\n  defines program\n\n    MoneyOrderTotalDemo()\n      stdout <- Stdout()\n\n      // === CREATE ORDER LINES ===\n\n      lines <- List() of OrderLine\n      lines += OrderLine(\"Widget\", 29.99#USD, 3)\n      lines += OrderLine(\"Gadget\", 45.50#USD, 2)\n      lines += OrderLine(\"Cable\", 9.99#USD, 5)\n\n      // === CALCULATE LINE TOTALS ===\n\n      for line in lines\n        lineTotal <- line.unitPrice * line.quantity\n        stdout.println(`${line.description}: ${lineTotal}`)\n\n      // === SUM ALL LINE TOTALS ===\n\n      orderTotal <- 0.00#USD\n      for line in lines\n        orderTotal += line.unitPrice * line.quantity\n\n      stdout.println(`Order total: ${orderTotal}`)\n\n      // === VERIFY INDIVIDUAL TOTALS ===\n\n      widgetTotal <- 29.99#USD * 3\n      stdout.println(`Widget total: ${widgetTotal}`)\n\n      gadgetTotal <- 45.50#USD * 2\n      stdout.println(`Gadget total: ${gadgetTotal}`)","migrationContext":"Java: BigDecimal.multiply(quantity).setScale(2, RoundingMode.HALF_UP), then stream().reduce(BigDecimal.ZERO, BigDecimal::add). Python: Decimal * int. Rust: no built-in money. Go: no built-in money. EK9: unitPrice * quantity with automatic precision, += for accumulation.","keywords":["accumulate","arithmetic","line item","money","multiply","order","price","quantity","sum","total"],"primaryTopics":["money arithmetic","order total calculation","money multiplication"],"typicalErrors":[{"error":"E50060","correct":"orderTotal += line.unitPrice * line.quantity","incorrect":"orderTotal += line.unitPrice.multiply(line.quantity)","explanation":"Money has no multiply() method — use the * operator directly on Money with an Integer or Float quantity. See ek9 -h E50060 for details."}],"companions":[]}
{"id":1201,"category":"Control Flow","question":"Check if a project has a deadline set, and warn if it is in the past.","url":"https://ek9.io/qa/QA1201.html","alternatePhrasings":["Use a guard to safely handle an optional deadline and compare it with today.","I need to check whether a deadline exists and if so whether it has passed.","In Java I'd check if deadline != null && deadline.isBefore(today). How does EK9 do this?","Guard a date lookup and then compare the result against the current date."],"answer":"Use an if guard to declare the deadline variable and check isSet in one expression. If the function returns an unset Date, the body is skipped. Inside the guard, compare the deadline against today.\n\nGUARD WITH COMPARISON\n  if deadline <- findDeadline() with deadline < today\n    stdout.println(\"Deadline has passed!\")\n\nThe 'with' clause adds a condition to the guard: the body runs only if the value is set AND the condition is true. This replaces null checks and isBefore() in one expression.\n\nSee Q1196 for guard with date. See Q74 for guard if. See Q1186 for date comparison.","ek9Example":"defines module qa.flow.guard.deadlinecheck\n\n  defines function\n\n    findDeadline()\n      <- rtn <- Date()\n\n    findActiveDeadline()\n      <- rtn <- 2024-03-01\n\n    findFutureDeadline()\n      <- rtn <- 2025-12-31\n\n  defines program\n\n    GuardDeadlineCheckDemo()\n      stdout <- Stdout()\n\n      today <- 2024-06-15\n\n      // === GUARD: NO DEADLINE SET ===\n\n      if deadline <- findDeadline()\n        stdout.println(`Deadline: ${deadline}`)\n      else\n        stdout.println(\"No deadline set for this project\")\n\n      // === GUARD: DEADLINE IN THE PAST ===\n\n      if deadline <- findActiveDeadline()\n        if deadline < today\n          stdout.println(`Warning: deadline ${deadline} is in the past`)\n        else\n          stdout.println(`Deadline ${deadline} is still upcoming`)\n\n      // === GUARD: DEADLINE IN THE FUTURE ===\n\n      if deadline <- findFutureDeadline()\n        if deadline > today\n          stdout.println(`On track: deadline ${deadline} is in the future`)\n\n      // === MULTIPLE DEADLINE CHECKS ===\n\n      if first <- findActiveDeadline()\n        if second <- findFutureDeadline()\n          gap <- second - first\n          stdout.println(`Gap between deadlines: ${gap}`)","migrationContext":"Java: Date d = findDeadline(); if (d != null && d.isBefore(today)). Python: if (d := find_deadline()) and d < today. Rust: if let Some(d) = find_deadline() { if d < today }. Go: if d := findDeadline(); d != nil && d.Before(today). EK9: if deadline <- findDeadline() with deadline < today — one expression for declare + isSet + compare.","keywords":["check","comparison","control flow","date","deadline","future","guard","past","today","with"],"primaryTopics":["guard with condition","date comparison","deadline checking"],"typicalErrors":[{"error":"E01072","correct":"      if deadline <- findDeadline()\n        stdout.println(`Deadline: ${deadline}`)\n      else","incorrect":"deadline <- findDeadline()\n      if deadline != null\n        if deadline.isBefore(today)\n          stdout.println(\"Overdue\")","explanation":"EK9 has no null and no isBefore() method. Use guard variables in if statements and comparison operators on Date. See ek9 -h E01072 for details."}],"companions":[]}
{"id":1202,"category":"Operators and Expressions","question":"Calculate the time gap between two scheduled events.","url":"https://ek9.io/qa/QA1202.html","alternatePhrasings":["Subtract two dates to find the duration between events.","I need to compute how long between a project start and its milestone.","In Java I'd use ChronoUnit.DAYS.between(). What is the EK9 equivalent?","Given two event dates, find the duration gap and compare it to a threshold."],"answer":"EK9 Date supports operator - with another Date, returning a Duration. Use Duration comparison to check if the gap meets a threshold.\n\nDATE SUBTRACTION\n  gap <- eventB.eventDate - eventA.eventDate   returns Duration\n\nDURATION COMPARISON\n  threshold <- P30D\n  if gap > threshold\n    stdout.println(\"Events are more than 30 days apart\")\n\nComponent access on Duration:\n  gap.days()    number of days\n  gap.months()  number of months\n\nSee Q1174 for duration between dates. See Q1188 for duration operations. See Q1186 for date comparison.","ek9Example":"defines module qa.operators.durationbetweenevents\n\n  defines record\n\n    ScheduledEvent\n      eventName as String: String()\n      eventDate as Date: Date()\n\n      ScheduledEvent() as pure\n        ->\n          theName as String\n          theDate as Date\n        this.eventName :=: theName\n        this.eventDate :=: theDate\n\n      default operator\n\n  defines program\n\n    DurationBetweenEventsDemo()\n      stdout <- Stdout()\n\n      // === CREATE EVENTS ===\n\n      launch <- ScheduledEvent(\"Product Launch\", 2024-01-15)\n      milestone <- ScheduledEvent(\"Q2 Milestone\", 2024-06-30)\n      review <- ScheduledEvent(\"Annual Review\", 2024-12-31)\n\n      // === CALCULATE GAPS ===\n\n      launchToMilestone <- milestone.eventDate - launch.eventDate\n      stdout.println(`Launch to Milestone: ${launchToMilestone}`)\n      stdout.println(`Days: ${launchToMilestone.days()}`)\n\n      milestoneToReview <- review.eventDate - milestone.eventDate\n      stdout.println(`Milestone to Review: ${milestoneToReview}`)\n\n      // === COMPARE DURATIONS ===\n\n      if launchToMilestone < milestoneToReview\n        stdout.println(\"First half is shorter than second half\")\n\n      // === CHECK AGAINST THRESHOLD ===\n\n      threshold <- P90D\n      if launchToMilestone > threshold\n        stdout.println(`Launch to milestone exceeds ${threshold}`)\n\n      // === TOTAL SPAN ===\n\n      totalSpan <- review.eventDate - launch.eventDate\n      stdout.println(`Total span: ${totalSpan}`)","migrationContext":"Java: ChronoUnit.DAYS.between(d1, d2) or Period.between(). Python: (d2 - d1).days. Rust: d2.signed_duration_since(d1). Go: t2.Sub(t1). EK9: eventB.eventDate - eventA.eventDate gives Duration directly, then compare with < > operators.","keywords":["between","compare","date","days","duration","events","gap","schedule","subtract","threshold"],"primaryTopics":["date subtraction","duration comparison","event gap"],"typicalErrors":[{"error":"E50060","correct":"      stdout.println(`Launch to milestone exceeds ${threshold}`)","incorrect":"      stdout.println(`Launch to milestone exceeds ${launch.eventDate.between(review.eventDate)}`)","explanation":"EK9 Date has no between() method (and no ChronoUnit) — subtract two Dates with the - operator to get a Duration. See ek9 -h E50060 for details."}],"companions":[]}
{"id":1203,"category":"Streams and Pipelines","question":"Filter a product list to items over 50 USD and sort by price ascending.","url":"https://ek9.io/qa/QA1203.html","alternatePhrasings":["Use a stream pipeline to filter products by price and sort the result.","I need to find all expensive items from a list and display them in order.","In Java I'd use stream().filter(p -> p.getPrice() > 50).sorted(). What is the EK9 equivalent?","Given products with Money prices, filter by a named threshold and sort."],"answer":"Define a pure predicate function for the filter and a named constant for the threshold. Stream pipeline: cat | filter by | sort | collect.\n\nNAMED THRESHOLD\n  threshold <- 50.00#USD    named constant avoids magic literal\n\nPREDICATE FUNCTION\n  isExpensive() as pure\n    -> item as Product\n    <- rtn as Boolean: item.price > threshold\n\nPIPELINE\n  expensive <- cat products | filter by isExpensive | sort | collect as List of Product\n\nThe filter stage applies the predicate. The sort stage uses <=> from default operator. Named constants make the threshold explicit and changeable.\n\nSee Q1134 for filter-map-sort. See Q1189 for money comparison. See Q1184 for named constants.","ek9Example":"defines module qa.streams.filterexpensiveproducts\n\n  defines record\n\n    Product\n      productName as String: String()\n      price as Money: Money()\n\n      Product() as pure\n        ->\n          theName as String\n          thePrice as Money\n        this.productName :=: theName\n        this.price :=: thePrice\n\n      default operator\n\n  defines function\n\n    isExpensive() as pure\n      -> item as Product\n      <- rtn as Boolean?\n      threshold <- 50.00#USD\n      rtn: item.price > threshold\n\n  defines program\n\n    FilterExpensiveProductsDemo()\n      stdout <- Stdout()\n\n      // === CREATE PRODUCT LIST ===\n\n      products <- List() of Product\n      products += Product(\"Mouse\", 19.99#USD)\n      products += Product(\"Keyboard\", 49.99#USD)\n      products += Product(\"Monitor\", 299.99#USD)\n      products += Product(\"Headset\", 79.50#USD)\n      products += Product(\"Cable\", 9.99#USD)\n      products += Product(\"Webcam\", 65.00#USD)\n\n      // === FILTER AND SORT ===\n\n      expensive <- cat products | filter by isExpensive | sort | collect as List of Product\n      stdout.println(\"Expensive products (sorted):\")\n      cat expensive > stdout\n\n      // === DIRECT OUTPUT ===\n\n      stdout.println(\"Expensive to stdout:\")\n      cat products | filter by isExpensive | sort > stdout","migrationContext":"Java: stream().filter(p -> p.getPrice().compareTo(threshold) > 0).sorted(). Python: [p for p in products if p.price > 50]. Rust: iter().filter(|p| p.price > 50).sorted(). Go: manual loop with append. EK9: cat products | filter by isExpensive | sort | collect — pipe syntax with named predicate.","keywords":["expensive","filter","money","named constant","pipeline","predicate","product","sort","stream","threshold"],"primaryTopics":["stream filter with money","named threshold","product filtering"],"typicalErrors":[{"error":"E50060","correct":"expensive <- cat products | filter by isExpensive | sort | collect as List of Product","incorrect":"expensive <- products.stream().filter(p -> p.price > 50).collect()","explanation":"EK9 uses stream pipelines with named predicate functions, not lambda expressions or method calls. See ek9 -h E50060 for details."}],"companions":[]}
{"id":1204,"category":"Classes and OOP","question":"Define an Employee record with name, hire date, and department, then sort employees by hire date.","url":"https://ek9.io/qa/QA1204.html","alternatePhrasings":["Create an Employee record with a Date field and sort a list of employees chronologically.","I need to define an employee type and order employees by when they were hired.","In Java I'd use Comparator.comparing(Employee::getHireDate). How does EK9 sort by a date field?","Build an Employee record with hire date and use a stream pipeline to sort by seniority."],"answer":"Define a record with fields in the desired sort priority order. The 'default operator' generates <=> comparing fields in declaration order. Put hireDate first for chronological sorting.\n\nFIELD ORDER MATTERS\n  Employee\n    hireDate as Date: Date()     first field = primary sort key\n    employeeName as String: String()  second field = secondary sort key\n    department as String: String()    third field = tertiary sort key\n\nSORT BY HIRE DATE\n  sorted <- cat employees | sort | collect as List of Employee\n\nSince hireDate is declared first, the default <=> operator sorts chronologically. Stream pipelines use the type's <=> operator automatically.\n\nSee Q1199 for sorting by date+time. See Q1135 for record sort pipeline. See Q1195 for mixed-type record.","ek9Example":"defines module qa.classesandoop.employeehiredate\n\n  defines record\n\n    Employee\n      hireDate as Date: Date()\n      employeeName as String: String()\n      department as String: String()\n\n      Employee() as pure\n        ->\n          theHireDate as Date\n          theName as String\n          theDepartment as String\n        this.hireDate :=: theHireDate\n        this.employeeName :=: theName\n        this.department :=: theDepartment\n\n      default operator\n\n  defines program\n\n    EmployeeHireDateDemo()\n      stdout <- Stdout()\n\n      // === CREATE EMPLOYEES ===\n\n      employees <- List() of Employee\n      employees += Employee(2024-06-15, \"Alice\", \"Engineering\")\n      employees += Employee(2023-01-10, \"Bob\", \"Marketing\")\n      employees += Employee(2024-01-05, \"Carol\", \"Engineering\")\n      employees += Employee(2022-09-20, \"Dave\", \"Operations\")\n\n      // === SORT BY HIRE DATE (field declaration order) ===\n\n      sorted <- cat employees | sort | collect as List of Employee\n      stdout.println(\"Employees by seniority:\")\n      cat sorted > stdout\n\n      // === MOST SENIOR (earliest hire date) ===\n\n      mostSenior <- cat employees | sort | head 1 | collect as List of Employee\n      stdout.println(\"Most senior:\")\n      cat mostSenior > stdout\n\n      // === NEWEST HIRES ===\n\n      newest <- cat employees | sort | tail 2 | collect as List of Employee\n      stdout.println(\"Newest hires:\")\n      cat newest > stdout\n\n      // === COMPARE TWO EMPLOYEES ===\n\n      alice <- Employee(2024-06-15, \"Alice\", \"Engineering\")\n      bob <- Employee(2023-01-10, \"Bob\", \"Marketing\")\n      if bob < alice\n        stdout.println(\"Bob was hired before Alice\")","migrationContext":"Java: employees.stream().sorted(Comparator.comparing(Employee::getHireDate)). Python: sorted(employees, key=lambda e: e.hire_date). Rust: sort_by_key(|e| e.hire_date). Go: sort.Slice with custom less. EK9: declare hireDate first + default operator + cat | sort | collect.","keywords":["chronological","date","default operator","employee","field order","hire date","record","seniority","sort","stream"],"primaryTopics":["sort by date field","record field order","employee sorting"],"typicalErrors":[{"error":"E06820","correct":"      default operator\n\n  defines program","incorrect":"default operator\n      department as String: String()\n\n      (fields after default operator)","explanation":"The 'default operator' must be the LAST declaration in the record body. Placing fields or methods after it triggers E06820. See ek9 -h E06820 for details."}],"companions":[]}
{"id":1205,"category":"Operators and Expressions","question":"Check if a Time value falls within business hours (09:00 to 17:00).","url":"https://ek9.io/qa/QA1205.html","alternatePhrasings":["Validate that a time is between start and end of business using is in.","I need to determine whether a meeting time falls within office hours.","In Java I'd check time.isAfter(start) && time.isBefore(end). What is the EK9 equivalent?","Given named time boundaries, check if a current time is within the range."],"answer":"EK9 Time supports the 'is in' operator with a range for clean boundary checks. Use named constants for the boundaries.\n\nNAMED BOUNDARIES\n  businessStart <- 09:00\n  businessEnd <- 17:00\n\nRANGE CHECK\n  if currentTime is in businessStart ... businessEnd\n    stdout.println(\"Within business hours\")\n\nThe 'is in start ... end' syntax is inclusive on both ends. It works with any comparable type: Time, Date, Integer, Float, etc. Named constants avoid magic literals and make the range self-documenting.\n\nSee Q1102 for is in with ranges. See Q1187 for time comparison. See Q1184 for named constants.","ek9Example":"defines module qa.operators.timerangecheck\n\n  defines program\n\n    TimeRangeCheckDemo()\n      stdout <- Stdout()\n\n      // === NAMED TIME BOUNDARIES ===\n\n      businessStart <- 09:00\n      businessEnd <- 17:00\n\n      // === WITHIN BUSINESS HOURS ===\n\n      morningMeeting <- 10:30\n      if morningMeeting is in businessStart ... businessEnd\n        stdout.println(`${morningMeeting} is within business hours`)\n\n      // === OUTSIDE BUSINESS HOURS ===\n\n      earlyCall <- 07:45\n      if not (earlyCall is in businessStart ... businessEnd)\n        stdout.println(`${earlyCall} is outside business hours`)\n\n      // === ON THE BOUNDARY (INCLUSIVE) ===\n\n      nineSharp <- 09:00\n      fivePm <- 17:00\n      startInRange <- nineSharp is in businessStart ... businessEnd\n      endInRange <- fivePm is in businessStart ... businessEnd\n      stdout.println(`09:00 in range: ${startInRange}`)\n      stdout.println(`17:00 in range: ${endInRange}`)\n\n      // === MULTIPLE TIME CHECKS ===\n\n      times <- [08:00, 09:30, 12:00, 16:45, 18:30]\n      for checkTime in times\n        if checkTime is in businessStart ... businessEnd\n          stdout.println(`${checkTime}: office hours`)\n        else\n          stdout.println(`${checkTime}: after hours`)","migrationContext":"Java: !time.isBefore(start) && !time.isAfter(end) — two method calls, easy to get wrong. Python: start <= t <= end — chained comparison. Rust: (start..=end).contains(&t). Go: !t.Before(start) && !t.After(end). EK9: time is in start ... end — one readable expression, inclusive bounds.","keywords":["boundary","business hours","check","inclusive","is in","named constant","range","schedule","time","validate"],"primaryTopics":["time range check","is in with time","named constants"],"typicalErrors":[],"companions":[]}
{"id":1206,"category":"Control Flow","question":"Categorise a dimension measurement as small, medium, or large using named thresholds.","url":"https://ek9.io/qa/QA1206.html","alternatePhrasings":["Use if-else with Dimension comparisons and named constants to classify a measurement.","I need to bucket a measurement into size categories using named thresholds.","In Java I'd use if-else chains with comparisons to classify dimensions. What is the EK9 approach?","Given a Dimension value, classify it into small, medium, or large with clear boundaries."],"answer":"Use named Dimension constants for thresholds and if-else chains for classification. Named constants make the boundaries self-documenting.\n\nNAMED THRESHOLDS\n  smallLimit <- 1.0m\n  largeLimit <- 5.0m\n\nCLASSIFICATION\n  if measurement < smallLimit\n    category: \"small\"\n  else if measurement < largeLimit\n    category: \"medium\"\n  else\n    category: \"large\"\n\nDimension supports comparison operators (<, >, <=, >=) for same-unit values. Named constants avoid magic literals and make the classification logic readable.\n\nSee Q1191 for dimension comparison. See Q1184 for named constants. See Q62 for if-else chains.","ek9Example":"defines module qa.flow.switchoncategory\n\n  defines function\n\n    shorterOf() as pure\n      ->\n        left as Dimension\n        right as Dimension\n      <- rtn as Dimension: left <? right\n\n    longerOf() as pure\n      ->\n        left as Dimension\n        right as Dimension\n      <- rtn as Dimension: left >? right\n\n    classify() as pure\n      -> measurement as Dimension\n      <- category as String: \"unknown\"\n      smallLimit <- 1.0m\n      largeLimit <- 5.0m\n      if measurement < smallLimit\n        category: \"small\"\n      else if measurement < largeLimit\n        category: \"medium\"\n      else\n        category: \"large\"\n\n  defines program\n\n    SwitchOnCategoryDemo()\n      stdout <- Stdout()\n\n      // === CLASSIFY MEASUREMENTS ===\n\n      measurements <- [0.5m, 1.0m, 2.5m, 5.0m, 10.0m]\n      for measurement in measurements\n        category <- classify(measurement)\n        stdout.println(`${measurement} -> ${category}`)\n\n      // === COALESCING COMPARISON ===\n\n      beamA <- 0.5m\n      beamB <- 2.5m\n      shortest <- shorterOf(beamA, beamB)\n      longest <- longerOf(beamA, beamB)\n      stdout.println(`Shortest: ${shortest}`)\n      stdout.println(`Longest: ${longest}`)\n\n      // === STRING REPRESENTATION ===\n\n      panel <- 2.5m\n      panelStr <- $panel\n      stdout.println(`Panel dimension: ${panelStr}`)","migrationContext":"Java: if (dim < 1.0) small; else if (dim < 5.0) medium; else large — plain doubles, no units. Python: similar if-else, no unit type. Rust: match with guards. Go: if-else with float64. EK9: if-else with Dimension comparison, unit-safe, named constants for thresholds.","keywords":["categorise","classify","dimension","if else","large","measurement","medium","named constant","small","threshold"],"primaryTopics":["dimension classification","named thresholds","if-else categorisation"],"typicalErrors":[{"error":"E50060","correct":"if measurement < smallLimit","incorrect":"if measurement.getValue() < 1.0","explanation":"Dimension supports comparison operators directly. No getValue() method needed. Use named Dimension constants for thresholds instead of magic numeric literals. See ek9 -h E50060 for details."}],"companions":[]}
{"id":1207,"category":"Operators and Expressions","question":"Build a list of Colour values and check if a specific colour is in the palette.","url":"https://ek9.io/qa/QA1207.html","alternatePhrasings":["Create a palette of colours and test membership using the contains operator.","I need to verify whether a given colour exists in a predefined set.","In CSS I'd compare hex codes as strings. How does EK9 check colour membership in a list?","Given a list of Colour literals, check if a target colour is present."],"answer":"EK9 Colour uses hex literals (#RRGGBB). Store colours in a List and use the 'contains' operator for membership testing.\n\nPALETTE\n  palette <- [#FF0000, #00FF00, #0000FF]\n\nMEMBERSHIP CHECK\n  if palette contains targetColour\n    stdout.println(\"Colour found in palette\")\n\nThe 'contains' operator checks element membership in a List. It uses the == operator internally, so it works with any type that supports equality. Colour literals and the == operator make palette management simple and type-safe.\n\nSee Q1159 for list contains. See Q1190 for colour equality. See Q1101 for is in with lists.","ek9Example":"defines module qa.operators.colourpalette\n\n  defines program\n\n    ColourPaletteDemo()\n      stdout <- Stdout()\n\n      // === BUILD PALETTE ===\n\n      red <- #FF0000\n      green <- #00FF00\n      blue <- #0000FF\n      palette <- [red, green, blue]\n\n      // === MEMBERSHIP CHECK ===\n\n      targetColour <- #FF0000\n      if palette contains targetColour\n        stdout.println(`${targetColour} is in the palette`)\n\n      // === COLOUR NOT IN PALETTE ===\n\n      yellow <- #FFFF00\n      if not (palette contains yellow)\n        stdout.println(`${yellow} is not in the palette`)\n\n      // === CHECK MULTIPLE COLOURS ===\n\n      candidates <- [#FF0000, #FFFF00, #0000FF, #FF00FF]\n      for candidate in candidates\n        if palette contains candidate\n          stdout.println(`${candidate}: in palette`)\n        else\n          stdout.println(`${candidate}: not in palette`)\n\n      // === ADD TO PALETTE AND RECHECK ===\n\n      extendedPalette <- palette + yellow\n      if extendedPalette contains yellow\n        stdout.println(`Yellow added to extended palette`)\n\n      // === STREAM OUTPUT ===\n\n      stdout.println(\"Full palette:\")\n      cat palette > stdout","migrationContext":"Java: list.contains(color) with java.awt.Color. Python: color in palette with tuples or custom class. JavaScript: array.includes(color) with string hex codes. CSS: string comparison. EK9: palette contains targetColour — operator syntax, typed Colour values, no string comparison needed.","keywords":["check","collection","colour","contains","hex","list","literal","membership","palette","rgb"],"primaryTopics":["colour list membership","contains operator","colour palette"],"typicalErrors":[],"companions":[]}
{"id":1208,"category":"Operators and Expressions","question":"Find the earlier of two delivery dates, even if one is unknown.","url":"https://ek9.io/qa/QA1208.html","alternatePhrasings":["How do I pick the minimum of two optional dates using <? coalescing?","A supplier sends two possible delivery dates but one might be missing — pick the earlier one.","In Java I'd need null checks and compareTo to find the earlier date. What does EK9 offer?","Migrating from Python where I use min(d1, d2) with None checks — what is the EK9 equivalent?"],"answer":"The <? coalescing minimum operator returns the lesser of two values, handling unset gracefully. Wrap it in a pure function.\n\n  earlierOf() as pure\n    -> left as Date, right as Date\n    <- rtn as Date: left <? right\n\nIf both dates are set, <? returns the earlier one. If one is unset, it returns the set one. If both are unset, the result is unset. This is a COMPARISON operator — it does NOT assign anything.\n\nDo not confuse <? with :=? (guarded assignment). The :=? operator assigns a value only if the target variable is unset — it is not a comparison. See Q1226 for a side-by-side contrast.\n\nSee Q1092 for min/max coalescing overview. ","ek9Example":"defines module qa.operators.coalescemin.dates\n\n  defines function\n\n    earlierOf() as pure\n      ->\n        left as Date\n        right as Date\n      <- rtn as Date: left <? right\n\n  defines program\n\n    CoalesceMinDatesDemo()\n      stdout <- Stdout()\n\n      // === BOTH DATES SET — returns the earlier ===\n\n      deliveryA <- 2024-11-15\n      deliveryB <- 2024-11-08\n      earliest <- earlierOf(deliveryA, deliveryB)\n      stdout.println(`Earliest delivery: ${earliest}`)\n\n      // === ONE DATE UNSET — returns the set one ===\n\n      confirmedDate <- 2024-12-01\n      pendingDate <- Date()\n      available <- earlierOf(confirmedDate, pendingDate)\n      stdout.println(`Available date: ${available}`)\n\n      // === BOTH DATES UNSET — result is unset ===\n\n      unknownA <- Date()\n      unknownB <- Date()\n      noDate <- earlierOf(unknownA, unknownB)\n      if noDate?\n        stdout.println(`Date: ${noDate}`)\n      else\n        stdout.println(\"No delivery date available\")\n\n      // === INLINE USAGE ===\n\n      shippingDate <- 2024-10-20\n      warehouseDate <- 2024-10-25\n      pickupDate <- shippingDate <? warehouseDate\n      stdout.println(`Pickup by: ${pickupDate}`)","migrationContext":"Java: d1 == null ? d2 : d2 == null ? d1 : d1.isBefore(d2) ? d1 : d2. Python: min(d for d in [d1, d2] if d is not None). Rust: [d1, d2].iter().flatten().min(). EK9: left <? right — one operator handles all cases.","keywords":["<?","coalescing","comparison","date","delivery","earlier","minimum","unset"],"primaryTopics":["<? coalescing minimum","date comparison","unset handling"],"typicalErrors":[{"error":"E50001","correct":"left <? right","incorrect":"Math.min(left, right)","explanation":"EK9 has no Math.min(); use the '<?' coalescing minimum operator, which also handles unset values. See ek9 -h E50001 for details."}],"companions":[]}
{"id":1209,"category":"Operators and Expressions","question":"Select the cheaper of two price quotes where one vendor hasn't responded.","url":"https://ek9.io/qa/QA1209.html","alternatePhrasings":["How do I find the minimum of two Money values when one might be unset?","A procurement system has two vendor quotes but one is pending — pick the cheaper safely.","In Java I'd need null checks before comparing BigDecimal prices. What does EK9 use?","Migrating from Go where I check nil before comparing prices — what is the EK9 pattern?"],"answer":"The <? coalescing minimum operator returns the lesser of two Money values, handling unset gracefully.\n\n  cheaperOf() as pure\n    -> left as Money, right as Money\n    <- rtn as Money: left <? right\n\nIf both prices are set, <? returns the cheaper one. If one is unset (vendor hasn't responded), it returns the available quote. If both are unset, the result is unset.\n\nThis is a COMPARISON operator — it picks the smaller value. Do not confuse it with :=? (guarded assignment), which assigns only if the target is unset. See Q1226 for a side-by-side contrast.\n\nSee Q1092 for min/max coalescing overview. See Q1200 for Money arithmetic.","ek9Example":"defines module qa.operators.coalescemin.money\n\n  defines function\n\n    cheaperOf() as pure\n      ->\n        left as Money\n        right as Money\n      <- rtn as Money: left <? right\n\n  defines program\n\n    CoalesceMinMoneyDemo()\n      stdout <- Stdout()\n\n      // === BOTH QUOTES RECEIVED — returns the cheaper ===\n\n      vendorA <- 149.99#USD\n      vendorB <- 129.50#USD\n      bestPrice <- cheaperOf(vendorA, vendorB)\n      stdout.println(`Best price: ${bestPrice}`)\n\n      // === ONE VENDOR HASN'T RESPONDED — returns the available quote ===\n\n      receivedQuote <- 199.00#USD\n      pendingQuote <- Money()\n      onlyOption <- cheaperOf(receivedQuote, pendingQuote)\n      stdout.println(`Available quote: ${onlyOption}`)\n\n      // === NEITHER VENDOR RESPONDED — result is unset ===\n\n      missingA <- Money()\n      missingB <- Money()\n      noQuote <- cheaperOf(missingA, missingB)\n      if noQuote?\n        stdout.println(`Quote: ${noQuote}`)\n      else\n        stdout.println(\"No vendor quotes available\")","migrationContext":"Java: a == null ? b : b == null ? a : a.compareTo(b) <= 0 ? a : b. Python: min(p for p in [a, b] if p is not None). Rust: no built-in money type. EK9: left <? right — one operator handles all cases.","keywords":["<?","cheaper","coalescing","minimum","money","price","quote","vendor"],"primaryTopics":["<? coalescing minimum","money comparison","price selection"],"typicalErrors":[{"error":"E50060","correct":"left <? right","incorrect":"left.min(right)","explanation":"Money has no min() method; use the <? coalescing-minimum operator, which handles unset values and returns the lesser. See ek9 -h E50060 for details."}],"companions":[]}
{"id":1210,"category":"Operators and Expressions","question":"Pick the shorter of two timeout durations for a retry policy.","url":"https://ek9.io/qa/QA1210.html","alternatePhrasings":["How do I select the minimum Duration when one timeout might be unconfigured?","A retry policy has primary and fallback timeouts — pick the shorter one safely.","In Java I'd need null checks before comparing Duration objects. What does EK9 use?","Migrating from Python where I use min() with None filtering for timedeltas — what is the EK9 way?"],"answer":"The <? coalescing minimum operator returns the shorter of two Duration values, handling unset gracefully.\n\n  shorterTimeout() as pure\n    -> left as Duration, right as Duration\n    <- rtn as Duration: left <? right\n\nIf both durations are set, <? returns the shorter one. If one is unset (unconfigured), it returns the configured timeout. If both are unset, the result is unset.\n\nThis is a COMPARISON operator — it picks the smaller value. Do not confuse it with :=? (guarded assignment), which assigns only if the target is unset. See Q1226 for a side-by-side contrast.\n\nSee Q1092 for min/max coalescing overview. See Q1188 for Duration operations.","ek9Example":"defines module qa.operators.coalescemin.duration\n\n  defines function\n\n    shorterTimeout() as pure\n      ->\n        left as Duration\n        right as Duration\n      <- rtn as Duration: left <? right\n\n  defines program\n\n    CoalesceMinDurationDemo()\n      stdout <- Stdout()\n\n      // === BOTH CONFIGURED — returns the shorter ===\n\n      primaryTimeout <- PT30S\n      fallbackTimeout <- PT15S\n      chosenTimeout <- shorterTimeout(primaryTimeout, fallbackTimeout)\n      stdout.println(`Chosen timeout: ${chosenTimeout}`)\n\n      // === ONE UNCONFIGURED — returns the configured one ===\n\n      configuredTimeout <- PT45S\n      missingTimeout <- Duration()\n      effectiveTimeout <- shorterTimeout(configuredTimeout, missingTimeout)\n      stdout.println(`Effective timeout: ${effectiveTimeout}`)\n\n      // === NEITHER CONFIGURED — result is unset ===\n\n      missingFirst <- Duration()\n      missingSecond <- Duration()\n      noTimeout <- shorterTimeout(missingFirst, missingSecond)\n      if noTimeout?\n        stdout.println(`Timeout: ${noTimeout}`)\n      else\n        stdout.println(\"No timeout configured\")","migrationContext":"Java: a == null ? b : b == null ? a : a.compareTo(b) <= 0 ? a : b. Python: min(d for d in [a, b] if d is not None). Go: if a == 0 { return b } else if b == 0 { return a } else { return min(a, b) }. EK9: left <? right — one operator handles all cases.","keywords":["<?","coalescing","duration","minimum","policy","retry","shorter","timeout"],"primaryTopics":["<? coalescing minimum","duration comparison","timeout selection"],"typicalErrors":[],"companions":[]}
{"id":1211,"category":"Operators and Expressions","question":"Find the earlier meeting time from two optional schedule slots.","url":"https://ek9.io/qa/QA1211.html","alternatePhrasings":["How do I select the minimum Time when one schedule slot might be unfilled?","Two calendar slots are offered but one might be empty — pick the earlier time.","In Java I'd check for null before comparing LocalTime objects. What does EK9 use?","Migrating from Kotlin where I use listOfNotNull then minOrNull — what is the EK9 way?"],"answer":"The <? coalescing minimum operator returns the earlier of two Time values, handling unset gracefully.\n\n  earlierTime() as pure\n    -> left as Time, right as Time\n    <- rtn as Time: left <? right\n\nIf both times are set, <? returns the earlier one. If one is unset (empty slot), it returns the available time. If both are unset, the result is unset.\n\nThis is a COMPARISON operator — it picks the smaller value. Do not confuse it with :=? (guarded assignment), which assigns only if the target is unset. See Q1226 for a side-by-side contrast.\n\nSee Q1092 for min/max coalescing overview. See Q1187 for Time comparison.","ek9Example":"defines module qa.operators.coalescemin.time\n\n  defines function\n\n    earlierTime() as pure\n      ->\n        left as Time\n        right as Time\n      <- rtn as Time: left <? right\n\n  defines program\n\n    CoalesceMinTimeDemo()\n      stdout <- Stdout()\n\n      // === BOTH SLOTS FILLED — returns the earlier ===\n\n      morningSlot <- 09:30\n      afternoonSlot <- 14:00\n      firstAvailable <- earlierTime(morningSlot, afternoonSlot)\n      stdout.println(`First available: ${firstAvailable}`)\n\n      // === ONE SLOT EMPTY — returns the filled one ===\n\n      confirmedSlot <- 11:00\n      emptySlot <- Time()\n      onlyOption <- earlierTime(confirmedSlot, emptySlot)\n      stdout.println(`Only option: ${onlyOption}`)\n\n      // === BOTH SLOTS EMPTY — result is unset ===\n\n      noSlotA <- Time()\n      noSlotB <- Time()\n      noTime <- earlierTime(noSlotA, noSlotB)\n      if noTime?\n        stdout.println(`Time: ${noTime}`)\n      else\n        stdout.println(\"No meeting time available\")","migrationContext":"Java: a == null ? b : b == null ? a : a.isBefore(b) ? a : b. Python: min(t for t in [a, b] if t is not None). Kotlin: listOfNotNull(a, b).minOrNull(). EK9: left <? right — one operator handles all cases.","keywords":["<?","coalescing","earlier","meeting","minimum","schedule","slot","time"],"primaryTopics":["<? coalescing minimum","time comparison","schedule selection"],"typicalErrors":[{"error":"E50060","correct":"left <? right","incorrect":"left.isBefore(right)","explanation":"Time has no isBefore() method; use the '<?' coalescing-minimum operator to pick the earlier value. See ek9 -h E50060 for details."}],"companions":[]}
{"id":1212,"category":"Operators and Expressions","question":"Get the lower of two sensor readings where one sensor might be offline.","url":"https://ek9.io/qa/QA1212.html","alternatePhrasings":["How do I find the minimum of two Integer values when one might be unset?","A monitoring system has two sensors but one could be offline — pick the lower reading.","In Java I'd need Optional and compareTo for nullable integers. What does EK9 use?","Migrating from C# where I use Math.Min with nullable checks — what is the EK9 pattern?"],"answer":"The <? coalescing minimum operator returns the lesser of two Integer values, handling unset gracefully.\n\n  lowerReading() as pure\n    -> left as Integer, right as Integer\n    <- rtn as Integer: left <? right\n\nIf both readings are set, <? returns the lower one. If one is unset (sensor offline), it returns the active sensor's reading. If both are unset, the result is unset.\n\nThis is a COMPARISON operator — it picks the smaller value. Do not confuse it with :=? (guarded assignment), which assigns only if the target is unset. See Q1226 for a side-by-side contrast.\n\nSee Q1092 for min/max coalescing overview. See Q1227 for all four coalescing operators.","ek9Example":"defines module qa.operators.coalescemin.integer\n\n  defines function\n\n    lowerReading() as pure\n      ->\n        left as Integer\n        right as Integer\n      <- rtn as Integer: left <? right\n\n  defines program\n\n    CoalesceMinIntegerDemo()\n      stdout <- Stdout()\n\n      // === BOTH SENSORS ONLINE — returns the lower reading ===\n\n      sensorA <- 42\n      sensorB <- 37\n      lowestReading <- lowerReading(sensorA, sensorB)\n      stdout.println(`Lowest reading: ${lowestReading}`)\n\n      // === ONE SENSOR OFFLINE — returns the active one ===\n\n      activeSensor <- 55\n      offlineSensor <- Integer()\n      onlyReading <- lowerReading(activeSensor, offlineSensor)\n      stdout.println(`Active reading: ${onlyReading}`)\n\n      // === BOTH SENSORS OFFLINE — result is unset ===\n\n      downA <- Integer()\n      downB <- Integer()\n      noReading <- lowerReading(downA, downB)\n      if noReading?\n        stdout.println(`Reading: ${noReading}`)\n      else\n        stdout.println(\"No sensor data available\")","migrationContext":"Java: Optional.ofNullable(a).flatMap(x -> Optional.ofNullable(b).map(y -> Math.min(x, y))).orElse(a != null ? a : b). Python: min(x for x in [a, b] if x is not None). EK9: left <? right — one operator handles all cases.","keywords":["<?","coalescing","integer","lower","minimum","offline","reading","sensor"],"primaryTopics":["<? coalescing minimum","integer comparison","sensor monitoring"],"typicalErrors":[{"error":"E50001","correct":"      <- rtn as Integer: left <? right","incorrect":"      <- rtn as Integer: Math.min(left, right)","explanation":"EK9 has no 'Math.min()'; use the '<?' coalescing-minimum operator, which also handles unset values. See ek9 -h E50001 for details."}],"companions":[]}
{"id":1213,"category":"Operators and Expressions","question":"Select the lower temperature from two weather stations.","url":"https://ek9.io/qa/QA1213.html","alternatePhrasings":["How do I find the minimum of two Float values when one station might be offline?","Two weather stations report temperature but one might be down — pick the lower reading.","In Java I'd need null checks before Math.min for nullable doubles. What does EK9 use?","Migrating from Python where I use min() with None filtering for floats — what is the EK9 pattern?"],"answer":"The <? coalescing minimum operator returns the lesser of two Float values, handling unset gracefully.\n\n  lowerTemp() as pure\n    -> left as Float, right as Float\n    <- rtn as Float: left <? right\n\nIf both temperatures are set, <? returns the lower one. If one is unset (station offline), it returns the active station's reading. If both are unset, the result is unset.\n\nThis is a COMPARISON operator — it picks the smaller value. Do not confuse it with :=? (guarded assignment), which assigns only if the target is unset. See Q1226 for a side-by-side contrast.\n\nSee Q1092 for min/max coalescing overview. See Q1227 for all four coalescing operators.","ek9Example":"defines module qa.operators.coalescemin.float\n\n  defines function\n\n    lowerTemp() as pure\n      ->\n        left as Float\n        right as Float\n      <- rtn as Float: left <? right\n\n  defines program\n\n    CoalesceMinFloatDemo()\n      stdout <- Stdout()\n\n      // === BOTH STATIONS REPORTING — returns the lower temperature ===\n\n      stationAlpha <- 18.5\n      stationBravo <- 22.3\n      coldest <- lowerTemp(stationAlpha, stationBravo)\n      stdout.println(`Coldest reading: ${coldest}`)\n\n      // === ONE STATION OFFLINE — returns the active one ===\n\n      activeStation <- 15.7\n      offlineStation <- Float()\n      onlyReading <- lowerTemp(activeStation, offlineStation)\n      stdout.println(`Active station: ${onlyReading}`)\n\n      // === BOTH STATIONS OFFLINE — result is unset ===\n\n      downAlpha <- Float()\n      downBravo <- Float()\n      noReading <- lowerTemp(downAlpha, downBravo)\n      if noReading?\n        stdout.println(`Temperature: ${noReading}`)\n      else\n        stdout.println(\"No weather data available\")","migrationContext":"Java: a == null ? b : b == null ? a : Math.min(a, b). Python: min(t for t in [a, b] if t is not None). Go: custom function with nil checks. EK9: left <? right — one operator handles all cases.","keywords":["<?","coalescing","float","lower","minimum","station","temperature","weather"],"primaryTopics":["<? coalescing minimum","float comparison","weather monitoring"],"typicalErrors":[{"error":"E50001","correct":"<- rtn as Float: left <? right","incorrect":"<- rtn as Float: Math.min(left, right)","explanation":"EK9 has no Math.min() — use the coalescing-minimum operator '<?', which returns the lesser value and handles unset operands. See ek9 -h E50001 for details."}],"companions":[]}
{"id":1214,"category":"Operators and Expressions","question":"Find the highest bid from two optional auction offers.","url":"https://ek9.io/qa/QA1214.html","alternatePhrasings":["How do I select the maximum of two Money values when one bidder might not have bid?","An auction receives two bids but one could be missing — pick the higher one safely.","In Java I'd need null checks before comparing BigDecimal bids. What does EK9 use?","Migrating from Python where I use max() with None filtering for prices — what is the EK9 pattern?"],"answer":"The >? coalescing maximum operator returns the greater of two Money values, handling unset gracefully.\n\n  higherBid() as pure\n    -> left as Money, right as Money\n    <- rtn as Money: left >? right\n\nIf both bids are set, >? returns the higher one. If one is unset (no bid placed), it returns the available bid. If both are unset, the result is unset.\n\nThis is a COMPARISON operator — it picks the larger value. Do not confuse it with :=? (guarded assignment), which assigns only if the target is unset. See Q1226 for a side-by-side contrast.\n\nSee Q1092 for min/max coalescing overview. See Q1209 for <? minimum on Money.","ek9Example":"defines module qa.operators.coalescemax.money\n\n  defines function\n\n    higherBid() as pure\n      ->\n        left as Money\n        right as Money\n      <- rtn as Money: left >? right\n\n  defines program\n\n    CoalesceMaxMoneyDemo()\n      stdout <- Stdout()\n\n      // === BOTH BIDS RECEIVED — returns the higher ===\n\n      bidderAlpha <- 5000.00#USD\n      bidderBravo <- 7500.00#USD\n      winningBid <- higherBid(bidderAlpha, bidderBravo)\n      stdout.println(`Winning bid: ${winningBid}`)\n\n      // === ONE BIDDER DIDN'T BID — returns the available bid ===\n\n      activeBid <- 3200.00#USD\n      missingBid <- Money()\n      onlyBid <- higherBid(activeBid, missingBid)\n      stdout.println(`Only bid: ${onlyBid}`)\n\n      // === NO BIDS — result is unset ===\n\n      missingA <- Money()\n      missingB <- Money()\n      bothMissing <- higherBid(missingA, missingB)\n      if bothMissing?\n        stdout.println(`Bid: ${bothMissing}`)\n      else\n        stdout.println(\"No bids received\")","migrationContext":"Java: a == null ? b : b == null ? a : a.compareTo(b) >= 0 ? a : b. Python: max(b for b in [a, b] if b is not None). Rust: no built-in money. EK9: left >? right — one operator handles all cases.","keywords":[">?","auction","bid","coalescing","higher","maximum","money","offer"],"primaryTopics":[">? coalescing maximum","money comparison","auction bidding"],"typicalErrors":[{"error":"E50060","correct":"      <- rtn as Money: left >? right","incorrect":"      <- rtn as Money: left.max(right)","explanation":"Money has no max() method; use the >? coalescing-maximum operator, which handles unset values and returns the greater. See ek9 -h E50060 for details."}],"companions":[]}
{"id":1215,"category":"Operators and Expressions","question":"Determine the latest expiry date from two optional certificates.","url":"https://ek9.io/qa/QA1215.html","alternatePhrasings":["How do I find the maximum of two Date values when one certificate might not exist?","A system has two TLS certificates but one might be missing — find the latest expiry.","In Java I'd need null checks and isAfter to find the latest date. What does EK9 use?","Migrating from Go where I check nil before comparing dates — what is the EK9 pattern?"],"answer":"The >? coalescing maximum operator returns the later of two Date values, handling unset gracefully.\n\n  latestExpiry() as pure\n    -> left as Date, right as Date\n    <- rtn as Date: left >? right\n\nIf both dates are set, >? returns the later one. If one is unset (certificate missing), it returns the available date. If both are unset, the result is unset.\n\nThis is a COMPARISON operator — it picks the larger value. Do not confuse it with :=? (guarded assignment), which assigns only if the target is unset. See Q1226 for a side-by-side contrast.\n\nSee Q1092 for min/max coalescing overview. See Q1208 for <? minimum on Date.","ek9Example":"defines module qa.operators.coalescemax.date\n\n  defines function\n\n    latestExpiry() as pure\n      ->\n        left as Date\n        right as Date\n      <- rtn as Date: left >? right\n\n  defines program\n\n    CoalesceMaxDateDemo()\n      stdout <- Stdout()\n\n      // === BOTH CERTIFICATES PRESENT — returns the later expiry ===\n\n      certA <- 2025-06-15\n      certB <- 2026-01-20\n      latestDate <- latestExpiry(certA, certB)\n      stdout.println(`Latest expiry: ${latestDate}`)\n\n      // === ONE CERTIFICATE MISSING — returns the available one ===\n\n      activeCert <- 2025-09-30\n      missingCert <- Date()\n      onlyExpiry <- latestExpiry(activeCert, missingCert)\n      stdout.println(`Available cert expires: ${onlyExpiry}`)\n\n      // === BOTH MISSING — result is unset ===\n\n      noCertA <- Date()\n      noCertB <- Date()\n      noExpiry <- latestExpiry(noCertA, noCertB)\n      if noExpiry?\n        stdout.println(`Expiry: ${noExpiry}`)\n      else\n        stdout.println(\"No certificates found\")","migrationContext":"Java: a == null ? b : b == null ? a : a.isAfter(b) ? a : b. Python: max(d for d in [a, b] if d is not None). Kotlin: listOfNotNull(a, b).maxOrNull(). EK9: left >? right — one operator handles all cases.","keywords":[">?","TLS","certificate","coalescing","date","expiry","latest","maximum"],"primaryTopics":[">? coalescing maximum","date comparison","certificate expiry"],"typicalErrors":[{"error":"E50060","correct":"left >? right","incorrect":"left.isAfter(right)","explanation":"Date has no isAfter() method, so left.isAfter(right) is unresolved — use the >? coalescing maximum operator instead. See ek9 -h E50060 for details."}],"companions":[]}
{"id":1216,"category":"Operators and Expressions","question":"Select the longer warranty period from two product options.","url":"https://ek9.io/qa/QA1216.html","alternatePhrasings":["How do I find the maximum of two Duration values when one product has no warranty info?","Two product variants have different warranty periods but one might be unspecified — pick the longer.","In Java I'd need null checks before comparing Duration objects. What does EK9 use?","Migrating from C# where I use TimeSpan comparison with nullable checks — what is the EK9 pattern?"],"answer":"The >? coalescing maximum operator returns the longer of two Duration values, handling unset gracefully.\n\n  longerWarranty() as pure\n    -> left as Duration, right as Duration\n    <- rtn as Duration: left >? right\n\nIf both durations are set, >? returns the longer one. If one is unset (no warranty info), it returns the available warranty. If both are unset, the result is unset.\n\nThis is a COMPARISON operator — it picks the larger value. Do not confuse it with :=? (guarded assignment), which assigns only if the target is unset. See Q1226 for a side-by-side contrast.\n\nSee Q1092 for min/max coalescing overview. See Q1210 for <? minimum on Duration.","ek9Example":"defines module qa.operators.coalescemax.duration\n\n  defines function\n\n    longerWarranty() as pure\n      ->\n        left as Duration\n        right as Duration\n      <- rtn as Duration: left >? right\n\n  defines program\n\n    CoalesceMaxDurationDemo()\n      stdout <- Stdout()\n\n      // === BOTH WARRANTIES KNOWN — returns the longer ===\n\n      standardWarranty <- P1Y\n      extendedWarranty <- P3Y\n      bestWarranty <- longerWarranty(standardWarranty, extendedWarranty)\n      stdout.println(`Best warranty: ${bestWarranty}`)\n\n      // === ONE WARRANTY UNKNOWN — returns the available one ===\n\n      knownWarranty <- P2Y\n      unknownWarranty <- Duration()\n      onlyWarranty <- longerWarranty(knownWarranty, unknownWarranty)\n      stdout.println(`Available warranty: ${onlyWarranty}`)\n\n      // === BOTH UNKNOWN — result is unset ===\n\n      missingA <- Duration()\n      missingB <- Duration()\n      noWarranty <- longerWarranty(missingA, missingB)\n      if noWarranty?\n        stdout.println(`Warranty: ${noWarranty}`)\n      else\n        stdout.println(\"No warranty information available\")","migrationContext":"Java: a == null ? b : b == null ? a : a.compareTo(b) >= 0 ? a : b. Python: max(d for d in [a, b] if d is not None). Go: custom function with nil checks on time.Duration. EK9: left >? right — one operator handles all cases.","keywords":[">?","coalescing","duration","longer","maximum","period","product","warranty"],"primaryTopics":[">? coalescing maximum","duration comparison","warranty selection"],"typicalErrors":[],"companions":[]}
{"id":1217,"category":"Operators and Expressions","question":"Get the higher score from two optional exam results.","url":"https://ek9.io/qa/QA1217.html","alternatePhrasings":["How do I find the maximum of two Integer values when one student might not have taken the exam?","A grading system has two exam scores but one might be absent — pick the higher score.","In Java I'd need Optional and Math.max for nullable integers. What does EK9 use?","Migrating from Python where I use max() with None filtering — what is the EK9 pattern?"],"answer":"The >? coalescing maximum operator returns the greater of two Integer values, handling unset gracefully.\n\n  higherScore() as pure\n    -> left as Integer, right as Integer\n    <- rtn as Integer: left >? right\n\nIf both scores are set, >? returns the higher one. If one is unset (exam not taken), it returns the available score. If both are unset, the result is unset.\n\nThis is a COMPARISON operator — it picks the larger value. Do not confuse it with :=? (guarded assignment), which assigns only if the target is unset. See Q1226 for a side-by-side contrast.\n\nSee Q1092 for min/max coalescing overview. See Q1212 for <? minimum on Integer.","ek9Example":"defines module qa.operators.coalescemax.integer\n\n  defines function\n\n    higherScore() as pure\n      ->\n        left as Integer\n        right as Integer\n      <- rtn as Integer: left >? right\n\n  defines program\n\n    CoalesceMaxIntegerDemo()\n      stdout <- Stdout()\n\n      // === BOTH EXAMS TAKEN — returns the higher score ===\n\n      midtermScore <- 78\n      practicalScore <- 85\n      bestScore <- higherScore(midtermScore, practicalScore)\n      stdout.println(`Best score: ${bestScore}`)\n\n      // === ONE EXAM NOT TAKEN — returns the available score ===\n\n      writtenScore <- 92\n      absentScore <- Integer()\n      onlyScore <- higherScore(writtenScore, absentScore)\n      stdout.println(`Available score: ${onlyScore}`)\n\n      // === BOTH EXAMS MISSED — result is unset ===\n\n      missedA <- Integer()\n      missedB <- Integer()\n      noScore <- higherScore(missedA, missedB)\n      if noScore?\n        stdout.println(`Score: ${noScore}`)\n      else\n        stdout.println(\"No exam scores recorded\")","migrationContext":"Java: a == null ? b : b == null ? a : Math.max(a, b). Python: max(s for s in [a, b] if s is not None). Go: custom function with nil checks. EK9: left >? right — one operator handles all cases.","keywords":[">?","coalescing","exam","grading","higher","integer","maximum","score"],"primaryTopics":[">? coalescing maximum","integer comparison","exam scoring"],"typicalErrors":[{"error":"E50001","correct":"      <- rtn as Integer: left >? right","incorrect":"      <- rtn as Integer: Math.max(left, right)","explanation":"EK9 has no 'Math.max' and the 'Math' reference does not resolve; use the '>?' coalescing maximum operator instead. See ek9 -h E50001 for details."}],"companions":[]}
{"id":1218,"category":"Operators and Expressions","question":"Select the earlier-or-equal time from two shift starts.","url":"https://ek9.io/qa/QA1218.html","alternatePhrasings":["How do I find the lesser-or-equal of two Time values when one shift might be unscheduled?","Two shifts have start times but one could be empty — pick the earlier or equal time.","In Java I'd need null checks before comparing with isBefore or equals. What does EK9 use?","Migrating from Python where I use min() for times — does EK9 have a less-than-or-equal coalescing?"],"answer":"The <=? coalescing operator returns the lesser-or-equal of two values, handling unset gracefully.\n\n  earlierOrEqual() as pure\n    -> left as Time, right as Time\n    <- rtn as Time: left <=? right\n\nIf both times are set and different, <=? returns the earlier one (same as <?). If both are equal, it returns that value. If one is unset, it returns the set one. If both unset, the result is unset.\n\nThe difference from <? appears when values are equal: <=? and <? both return the same result, but <=? semantically includes the equality case. The full coalescing family is <?, >?, <=?, >=?. See Q1227 for all four operators side by side.\n\nDo not confuse any coalescing operator with :=? (guarded assignment). See Q1226 for the contrast.","ek9Example":"defines module qa.operators.coalescelte.time\n\n  defines function\n\n    earlierOrEqual() as pure\n      ->\n        left as Time\n        right as Time\n      <- rtn as Time: left <=? right\n\n  defines program\n\n    CoalesceLteTimeDemo()\n      stdout <- Stdout()\n\n      // === BOTH SHIFTS SCHEDULED — returns the earlier ===\n\n      morningShift <- 06:00\n      dayShift <- 09:00\n      firstStart <- earlierOrEqual(morningShift, dayShift)\n      stdout.println(`First shift starts: ${firstStart}`)\n\n      // === EQUAL TIMES — returns the shared time ===\n\n      shiftA <- 08:00\n      shiftB <- 08:00\n      sameStart <- earlierOrEqual(shiftA, shiftB)\n      stdout.println(`Both start at: ${sameStart}`)\n\n      // === ONE SHIFT UNSCHEDULED — returns the scheduled one ===\n\n      scheduledShift <- 07:30\n      unscheduledShift <- Time()\n      onlyShift <- earlierOrEqual(scheduledShift, unscheduledShift)\n      stdout.println(`Scheduled shift: ${onlyShift}`)\n\n      // === BOTH UNSCHEDULED — result is unset ===\n\n      missingShift <- Time()\n      noShiftB <- Time()\n      bothMissing <- earlierOrEqual(missingShift, noShiftB)\n      if bothMissing?\n        stdout.println(`Shift: ${bothMissing}`)\n      else\n        stdout.println(\"No shifts scheduled\")","migrationContext":"Java: a == null ? b : b == null ? a : !a.isAfter(b) ? a : b. Python: min(a, b) handles equality naturally. Kotlin: listOfNotNull(a, b).minOrNull(). EK9: left <=? right — one operator handles all cases including equality.","keywords":["<=?","coalescing","earlier","equal","less-or-equal","schedule","shift","time"],"primaryTopics":["<=? coalescing less-or-equal","time comparison","shift scheduling"],"typicalErrors":[{"error":"E50030","correct":"<- rtn as Time: left <=? right","incorrect":"<- rtn as Time: left <= right","explanation":"'<=' is a comparison that yields Boolean, so assigning 'left <= right' to a Time return is a type mismatch; use the coalescing '<=?' which returns the lesser-or-equal Time and handles unset values. See ek9 -h E50030 for details."}],"companions":[]}
{"id":1219,"category":"Operators and Expressions","question":"Get the higher-or-equal temperature threshold.","url":"https://ek9.io/qa/QA1219.html","alternatePhrasings":["How do I find the greater-or-equal of two Float values when one threshold might be unset?","Two temperature thresholds are defined but one might be missing — pick the higher or equal.","In Java I'd need null checks before Math.max with nullable doubles. What does EK9 use?","Migrating from Go where I compare float64 with nil checks — does EK9 have a greater-or-equal coalescing?"],"answer":"The >=? coalescing operator returns the greater-or-equal of two values, handling unset gracefully.\n\n  higherOrEqual() as pure\n    -> left as Float, right as Float\n    <- rtn as Float: left >=? right\n\nIf both values are set and different, >=? returns the larger one (same as >?). If both are equal, it returns that value. If one is unset, it returns the set one. If both unset, the result is unset.\n\nThe full coalescing family is <?, >?, <=?, >=?. See Q1227 for all four operators side by side.\n\nDo not confuse any coalescing operator with :=? (guarded assignment). See Q1226 for the contrast.","ek9Example":"defines module qa.operators.coalescegte.float\n\n  defines function\n\n    higherOrEqual() as pure\n      ->\n        left as Float\n        right as Float\n      <- rtn as Float: left >=? right\n\n  defines program\n\n    CoalesceGteFloatDemo()\n      stdout <- Stdout()\n\n      // === BOTH THRESHOLDS SET — returns the higher ===\n\n      warningThreshold <- 35.0\n      criticalThreshold <- 40.0\n      upperLimit <- higherOrEqual(warningThreshold, criticalThreshold)\n      stdout.println(`Upper threshold: ${upperLimit}`)\n\n      // === EQUAL THRESHOLDS — returns the shared value ===\n\n      limitA <- 37.5\n      limitB <- 37.5\n      sameLimit <- higherOrEqual(limitA, limitB)\n      stdout.println(`Shared threshold: ${sameLimit}`)\n\n      // === ONE THRESHOLD UNSET — returns the set one ===\n\n      configuredLimit <- 42.0\n      missingLimit <- Float()\n      onlyLimit <- higherOrEqual(configuredLimit, missingLimit)\n      stdout.println(`Configured threshold: ${onlyLimit}`)\n\n      // === BOTH UNSET — result is unset ===\n\n      absentFirst <- Float()\n      absentSecond <- Float()\n      bothMissing <- higherOrEqual(absentFirst, absentSecond)\n      if bothMissing?\n        stdout.println(`Threshold: ${bothMissing}`)\n      else\n        stdout.println(\"No temperature threshold configured\")","migrationContext":"Java: a == null ? b : b == null ? a : a >= b ? a : b. Python: max(a, b) handles equality naturally. Kotlin: listOfNotNull(a, b).maxOrNull(). EK9: left >=? right — one operator handles all cases including equality.","keywords":[">=?","coalescing","equal","float","greater-or-equal","higher","temperature","threshold"],"primaryTopics":[">=? coalescing greater-or-equal","float comparison","threshold selection"],"typicalErrors":[],"companions":[]}
{"id":1220,"category":"Control Flow","question":"Set a default user name only if no name has been provided.","url":"https://ek9.io/qa/QA1220.html","alternatePhrasings":["How do I conditionally assign a String only when the variable is unset?","A registration form has an optional name field — apply a default guest name if empty.","In Java I'd check if name == null before assigning. What does EK9 use?","Migrating from Python where I use 'name = name or default' — what is the EK9 pattern?"],"answer":"The :=? guarded assignment operator assigns a value ONLY if the target variable is currently unset. If the variable already has a value, the assignment is skipped.\n\n  name <- String()\n  name :=? \"Guest\"\n  //name is now \"Guest\" because it was unset\n\n  name2 <- \"Alice\"\n  name2 :=? \"Guest\"\n  //name2 is still \"Alice\" — the :=? was skipped\n\nThis is an ASSIGNMENT operator — it sets a variable. \n\nSee Q1038 for :=? with config fallback chains. ","ek9Example":"defines module qa.flow.guardassign.string\n\n  defines function\n\n    lookupUserName()\n      <- rtn <- String()\n      //Simulates: no user name found in database\n\n    lookupDisplayName()\n      <- rtn <- \"RegisteredUser\"\n\n  defines program\n\n    GuardAssignStringDemo()\n      stdout <- Stdout()\n\n      // === VARIABLE IS UNSET — :=? assigns the default ===\n\n      userName <- String()\n      defaultName <- \"Guest\"\n      userName :=? defaultName\n      stdout.println(`User name: ${userName}`)\n\n      // === VARIABLE IS ALREADY SET — :=? is skipped ===\n\n      knownName <- lookupUserName()\n      knownName :=? \"Guest\"\n      stdout.println(`Known name preserved: ${knownName}`)\n\n      // === FALLBACK CHAIN — first set value wins ===\n\n      displayName <- String()\n      displayName :=? lookupUserName()\n      displayName :=? lookupDisplayName()\n      displayName :=? \"Anonymous\"\n      stdout.println(`Display name: ${displayName}`)\n\n      // === CONTRAST WITH := (regular assignment) ===\n      // Regular := always overwrites\n      // Guarded :=? only assigns when unset\n\n      greeting <- lookupDisplayName()\n      greeting :=? \"Hi\"\n      stdout.println(`Greeting preserved: ${greeting}`)","migrationContext":"Java: if (name == null) name = \"Guest\". Python: name = name or \"Guest\" (but fails for empty string). Go: if name == \"\" { name = \"Guest\" }. Kotlin: name = name ?: \"Guest\". EK9: name :=? \"Guest\" — one operator, correct tri-state semantics.","keywords":[":=?","assignment","conditional","default","guarded","name","string","unset"],"primaryTopics":[":=? guarded assignment","string defaults","conditional initialization"],"typicalErrors":[{"error":"E01073","correct":"      knownName <- \"Alice\"\n      knownName :=? \"Guest\"","incorrect":"      knownName <- \"Alice\"\n      if knownName == null\n        knownName := \"Guest\"","explanation":"EK9 has no null — writing '== null' references an unsupported literal; use the guarded assignment ':=?' to set a value only when the variable is currently unset. See ek9 -h E01073 for details."}],"companions":[]}
{"id":1221,"category":"Control Flow","question":"Apply a default port number only when the config port is unset.","url":"https://ek9.io/qa/QA1221.html","alternatePhrasings":["How do I conditionally assign an Integer only when the variable is unset?","A server config has an optional port — apply the default 8080 if not configured.","In Java I'd check if port == null before assigning a default. What does EK9 use?","Migrating from Go where I check if port == 0 — what is the EK9 pattern for default integers?"],"answer":"The :=? guarded assignment operator assigns a value ONLY if the target variable is currently unset. If the variable already has a value, the assignment is skipped.\n\n  port <- Integer()\n  defaultPort <- 8080\n  port :=? defaultPort\n  //port is now 8080 because it was unset\n\nThis is an ASSIGNMENT operator — it sets a variable. \n\nSee Q1038 for :=? with config fallback chains. ","ek9Example":"defines module qa.flow.guardassign.integer\n\n  defines function\n\n    loadPortFromEnvironment()\n      <- rtn <- Integer()\n      //Simulates: no port configured in environment\n\n    loadPortFromConfigFile()\n      <- rtn <- 9090\n\n  defines program\n\n    GuardAssignIntegerDemo()\n      stdout <- Stdout()\n\n      // === PORT IS UNSET — :=? assigns the default ===\n\n      port <- Integer()\n      defaultPort <- 8080\n      port :=? defaultPort\n      stdout.println(`Server port: ${port}`)\n\n      // === PORT IS ALREADY CONFIGURED — :=? is skipped ===\n\n      customPort <- loadPortFromConfigFile()\n      customPort :=? defaultPort\n      stdout.println(`Custom port preserved: ${customPort}`)\n\n      // === FALLBACK CHAIN — first set value wins ===\n\n      serverPort <- Integer()\n      serverPort :=? loadPortFromEnvironment()\n      serverPort :=? loadPortFromConfigFile()\n      fallbackPort <- 8080\n      serverPort :=? fallbackPort\n      stdout.println(`Resolved port: ${serverPort}`)","migrationContext":"Java: if (port == null) port = 8080. Python: port = port or 8080 (but fails for port 0). Go: if port == 0 { port = 8080 } (but 0 might be valid). Kotlin: port = port ?: 8080. EK9: port :=? defaultPort — correct tri-state semantics, 0 is a valid set value.","keywords":[":=?","assignment","config","default","guarded","integer","port","server"],"primaryTopics":[":=? guarded assignment","integer defaults","server configuration"],"typicalErrors":[{"error":"E01073","correct":"      port :=? defaultPort","incorrect":"      if port == null\n        port := defaultPort","explanation":"EK9 has no null. Use :=? to conditionally assign — it only sets the value when the variable is currently unset. See ek9 -h E01072 for details."}],"companions":[]}
{"id":1222,"category":"Control Flow","question":"Set a fallback deadline only if no deadline was specified.","url":"https://ek9.io/qa/QA1222.html","alternatePhrasings":["How do I conditionally assign a Date only when the variable is unset?","A project has an optional deadline — apply a default end-of-quarter date if missing.","In Java I'd check if deadline == null before assigning. What does EK9 use?","Migrating from Kotlin where I use the elvis operator for nullable dates — what is the EK9 pattern?"],"answer":"The :=? guarded assignment operator assigns a value ONLY if the target variable is currently unset. If the variable already has a value, the assignment is skipped.\n\n  deadline <- Date()\n  fallbackDate <- 2024-12-31\n  deadline :=? fallbackDate\n  //deadline is now 2024-12-31 because it was unset\n\nThis is an ASSIGNMENT operator — it sets a variable. \n\nSee Q1038 for :=? with config fallback chains. ","ek9Example":"defines module qa.flow.guardassign.date\n\n  defines function\n\n    lookupProjectDeadline()\n      <- rtn <- Date()\n      //Simulates: no deadline set for the project\n\n    lookupSprintEnd()\n      <- rtn <- 2024-09-30\n\n  defines program\n\n    GuardAssignDateDemo()\n      stdout <- Stdout()\n\n      // === DEADLINE IS UNSET — :=? assigns the fallback ===\n\n      deadline <- Date()\n      fallbackDate <- 2024-12-31\n      deadline :=? fallbackDate\n      stdout.println(`Deadline: ${deadline}`)\n\n      // === DEADLINE IS ALREADY SET — :=? is skipped ===\n\n      existingDeadline <- lookupSprintEnd()\n      existingDeadline :=? fallbackDate\n      stdout.println(`Existing deadline preserved: ${existingDeadline}`)\n\n      // === FALLBACK CHAIN — first set value wins ===\n\n      projectEnd <- Date()\n      projectEnd :=? lookupProjectDeadline()\n      projectEnd :=? lookupSprintEnd()\n      endOfYear <- 2024-12-31\n      projectEnd :=? endOfYear\n      stdout.println(`Project end: ${projectEnd}`)","migrationContext":"Java: if (deadline == null) deadline = LocalDate.of(2024, 12, 31). Python: deadline = deadline or default_date. Kotlin: deadline = deadline ?: defaultDate. Go: if deadline.IsZero() { deadline = fallback }. EK9: deadline :=? fallbackDate — one operator, correct tri-state semantics.","keywords":[":=?","assignment","date","deadline","default","fallback","guarded","project"],"primaryTopics":[":=? guarded assignment","date defaults","project deadline"],"typicalErrors":[{"error":"E01073","correct":"      deadline :=? fallbackDate","incorrect":"      if deadline == null\n        deadline := fallbackDate","explanation":"EK9 has no null. Use :=? to conditionally assign — it only sets the value when the variable is currently unset. See ek9 -h E01072 for details."}],"companions":[]}
{"id":1223,"category":"Control Flow","question":"Apply a minimum price only if the product price hasn't been set.","url":"https://ek9.io/qa/QA1223.html","alternatePhrasings":["How do I conditionally assign a Money value only when the variable is unset?","A product listing has an optional price — apply a default minimum if not set.","In Java I'd check if price == null before assigning a BigDecimal default. What does EK9 use?","Migrating from Python where I use 'price = price or Decimal(default)' — what is the EK9 pattern?"],"answer":"The :=? guarded assignment operator assigns a value ONLY if the target variable is currently unset. If the variable already has a value, the assignment is skipped.\n\n  price <- Money()\n  minimumPrice <- 9.99#USD\n  price :=? minimumPrice\n  //price is now 9.99 USD because it was unset\n\nThis is an ASSIGNMENT operator — it sets a variable. \n\nSee Q1038 for :=? with config fallback chains. ","ek9Example":"defines module qa.flow.guardassign.money\n\n  defines function\n\n    lookupCatalogPrice()\n      <- rtn <- Money()\n      //Simulates: no catalog price found\n\n    lookupSuggestedPrice()\n      <- rtn <- 24.99#USD\n\n  defines program\n\n    GuardAssignMoneyDemo()\n      stdout <- Stdout()\n\n      // === PRICE IS UNSET — :=? assigns the minimum ===\n\n      price <- Money()\n      minimumPrice <- 9.99#USD\n      price :=? minimumPrice\n      stdout.println(`Product price: ${price}`)\n\n      // === PRICE IS ALREADY SET — :=? is skipped ===\n\n      existingPrice <- 49.99#USD\n      existingPrice :=? minimumPrice\n      stdout.println(`Existing price preserved: ${existingPrice}`)\n\n      // === FALLBACK CHAIN — first set value wins ===\n\n      listPrice <- Money()\n      listPrice :=? lookupCatalogPrice()\n      listPrice :=? lookupSuggestedPrice()\n      listPrice :=? minimumPrice\n      stdout.println(`List price: ${listPrice}`)","migrationContext":"Java: if (price == null) price = new BigDecimal(\"9.99\"). Python: price = price or Decimal('9.99') (fails for Decimal(0)). Kotlin: price = price ?: minimumPrice. Go: no built-in money type. EK9: price :=? minimumPrice — one operator, correct tri-state semantics.","keywords":[":=?","assignment","default","guarded","minimum","money","price","product"],"primaryTopics":[":=? guarded assignment","money defaults","product pricing"],"typicalErrors":[{"error":"E01073","correct":"      price :=? minimumPrice","incorrect":"      if price == null\n        price := minimumPrice","explanation":"EK9 has no null. Use :=? to conditionally assign — it only sets the value when the variable is currently unset. See ek9 -h E01072 for details."}],"companions":[]}
{"id":1224,"category":"Control Flow","question":"Set a default tax rate only when the rate hasn't been configured.","url":"https://ek9.io/qa/QA1224.html","alternatePhrasings":["How do I conditionally assign a Float only when the variable is unset?","A tax calculator has an optional rate — apply the standard rate if not configured.","In Java I'd check if taxRate == null before assigning a double default. What does EK9 use?","Migrating from Go where I check if rate == 0.0 — what is the EK9 pattern for default floats?"],"answer":"The :=? guarded assignment operator assigns a value ONLY if the target variable is currently unset. If the variable already has a value, the assignment is skipped.\n\n  taxRate <- Float()\n  defaultRate <- 0.20\n  taxRate :=? defaultRate\n  //taxRate is now 0.20 because it was unset\n\nThis is an ASSIGNMENT operator — it sets a variable. \n\nSee Q1038 for :=? with config fallback chains. ","ek9Example":"defines module qa.flow.guardassign.float\n\n  defines function\n\n    lookupRegionalRate()\n      <- rtn <- Float()\n      //Simulates: no regional tax rate configured\n\n    lookupNationalRate()\n      <- rtn <- 0.15\n\n  defines program\n\n    GuardAssignFloatDemo()\n      stdout <- Stdout()\n\n      // === RATE IS UNSET — :=? assigns the default ===\n\n      taxRate <- Float()\n      defaultRate <- 0.20\n      taxRate :=? defaultRate\n      stdout.println(`Tax rate: ${taxRate}`)\n\n      // === RATE IS ALREADY CONFIGURED — :=? is skipped ===\n\n      customRate <- lookupNationalRate()\n      customRate :=? defaultRate\n      stdout.println(`Custom rate preserved: ${customRate}`)\n\n      // === FALLBACK CHAIN — first set value wins ===\n\n      effectiveRate <- Float()\n      effectiveRate :=? lookupRegionalRate()\n      effectiveRate :=? lookupNationalRate()\n      effectiveRate :=? defaultRate\n      stdout.println(`Effective rate: ${effectiveRate}`)","migrationContext":"Java: if (taxRate == null) taxRate = 0.20. Python: tax_rate = tax_rate or 0.20 (fails for rate 0.0). Go: if rate == 0.0 { rate = 0.20 } (but 0.0 might be valid). EK9: taxRate :=? defaultRate — correct tri-state semantics, 0.0 is a valid set value.","keywords":[":=?","assignment","configuration","default","float","guarded","rate","tax"],"primaryTopics":[":=? guarded assignment","float defaults","tax configuration"],"typicalErrors":[{"error":"E01073","correct":"      taxRate :=? defaultRate","incorrect":"      if taxRate == null\n        taxRate := defaultRate","explanation":"EK9 has no null. Use :=? to conditionally assign — it only sets the value when the variable is currently unset. See ek9 -h E01072 for details."}],"companions":[]}
{"id":1225,"category":"Control Flow","question":"Apply a default session timeout only if no timeout was set.","url":"https://ek9.io/qa/QA1225.html","alternatePhrasings":["How do I conditionally assign a Duration only when the variable is unset?","A web application has an optional session timeout — apply a default if not configured.","In Java I'd check if timeout == null before assigning a Duration default. What does EK9 use?","Migrating from C# where I use '??' for nullable TimeSpan — what is the EK9 pattern?"],"answer":"The :=? guarded assignment operator assigns a value ONLY if the target variable is currently unset. If the variable already has a value, the assignment is skipped.\n\n  timeout <- Duration()\n  defaultTimeout <- PT30M\n  timeout :=? defaultTimeout\n  //timeout is now 30 minutes because it was unset\n\nThis is an ASSIGNMENT operator — it sets a variable. \n\nSee Q1038 for :=? with config fallback chains. ","ek9Example":"defines module qa.flow.guardassign.duration\n\n  defines function\n\n    loadTimeoutFromConfig()\n      <- rtn <- Duration()\n      //Simulates: no timeout in configuration\n\n    loadTimeoutFromProfile()\n      <- rtn <- PT15M\n\n  defines program\n\n    GuardAssignDurationDemo()\n      stdout <- Stdout()\n\n      // === TIMEOUT IS UNSET — :=? assigns the default ===\n\n      timeout <- Duration()\n      defaultTimeout <- PT30M\n      timeout :=? defaultTimeout\n      stdout.println(`Session timeout: ${timeout}`)\n\n      // === TIMEOUT IS ALREADY CONFIGURED — :=? is skipped ===\n\n      customTimeout <- PT1H\n      customTimeout :=? defaultTimeout\n      stdout.println(`Custom timeout preserved: ${customTimeout}`)\n\n      // === FALLBACK CHAIN — first set value wins ===\n\n      sessionTimeout <- Duration()\n      sessionTimeout :=? loadTimeoutFromConfig()\n      sessionTimeout :=? loadTimeoutFromProfile()\n      sessionTimeout :=? defaultTimeout\n      stdout.println(`Effective timeout: ${sessionTimeout}`)","migrationContext":"Java: if (timeout == null) timeout = Duration.ofMinutes(30). Python: timeout = timeout or timedelta(minutes=30). C#: timeout = timeout ?? TimeSpan.FromMinutes(30). Go: if timeout == 0 { timeout = 30 * time.Minute }. EK9: timeout :=? defaultTimeout — one operator, correct tri-state semantics.","keywords":[":=?","assignment","default","duration","guarded","session","timeout","web"],"primaryTopics":[":=? guarded assignment","duration defaults","session timeout"],"typicalErrors":[{"error":"E01073","correct":"      timeout :=? defaultTimeout","incorrect":"      if timeout == null\n        timeout := defaultTimeout","explanation":"EK9 has no null. Use :=? to conditionally assign — it only sets the value when the variable is currently unset. See ek9 -h E01072 for details."}],"companions":[]}
{"id":1226,"category":"Operators and Expressions","question":"Demonstrate the difference between <? coalescing and :=? guarded assignment.","url":"https://ek9.io/qa/QA1226.html","alternatePhrasings":["What is the distinction between <? and :=? in EK9?","I keep confusing <? coalescing with :=? guarded assignment — show them side by side.","In Java I mix up Math.min with null-check assignment. How are these separate in EK9?","Show the two operators that look similar but do completely different things."],"answer":"<? and :=? are DIFFERENT operations despite the similar ? suffix.\n\n<? is a COMPARISON — it returns the lesser of two values:\n  cheaperOf() as pure\n    -> left as Money, right as Money\n    <- rtn as Money: left <? right\n  //Returns the smaller Money value, handles unset\n\n:=? is an ASSIGNMENT — it sets a variable only if unset:\n  price <- Money()\n  price :=? 9.99#USD\n  //Assigns 9.99 USD because price was unset\n\nKEY DIFFERENCES:\n- <? compares two values and produces a result. It goes inside pure functions.\n- :=? modifies a variable in place. It is an assignment, not a comparison.\n- <? has a companion >? (coalescing maximum). :=? has no companion.\n- <? always evaluates both sides. :=? skips the right side if the target is already set.\n\nSee Q1092 for <? and >? coalescing. See Q1038 for :=? fallback chains. See Q1227 for all four coalescing operators.","ek9Example":"defines module qa.operators.coalescevsguard\n\n  defines function\n\n    // <? is a COMPARISON — used inside pure functions\n    cheaperOf() as pure\n      ->\n        left as Money\n        right as Money\n      <- rtn as Money: left <? right\n\n    earlierOf() as pure\n      ->\n        left as Date\n        right as Date\n      <- rtn as Date: left <? right\n\n  defines program\n\n    CoalesceVsGuardDemo()\n      stdout <- Stdout()\n\n      // ============================================================\n      // PART 1: <? COALESCING MINIMUM — compares two values\n      // ============================================================\n\n      // <? returns the LESSER of two values\n      vendorA <- 149.99#USD\n      vendorB <- 129.50#USD\n      bestPrice <- cheaperOf(vendorA, vendorB)\n      stdout.println(`Cheaper quote (both set): ${bestPrice}`)\n\n      // <? with one unset — returns the set value\n      knownQuote <- 199.00#USD\n      missingQuote <- Money()\n      availableQuote <- cheaperOf(knownQuote, missingQuote)\n      stdout.println(`Available quote (one unset): ${availableQuote}`)\n\n      // ============================================================\n      // PART 2: :=? GUARDED ASSIGNMENT — sets a variable if unset\n      // ============================================================\n\n      // :=? assigns ONLY IF the target is unset\n      price <- Money()\n      minimumPrice <- 9.99#USD\n      price :=? minimumPrice\n      stdout.println(`Default price applied: ${price}`)\n\n      // :=? with already-set target — assignment is skipped\n      existingPrice <- 49.99#USD\n      existingPrice :=? minimumPrice\n      stdout.println(`Existing price preserved: ${existingPrice}`)\n\n      // ============================================================\n      // PART 3: SAME TYPES, DIFFERENT OPERATIONS\n      // ============================================================\n\n      // Both operations work on Date, but do different things:\n\n      // <? picks the earlier of two dates\n      dateA <- 2024-11-15\n      dateB <- 2024-11-08\n      earlierDate <- earlierOf(dateA, dateB)\n      stdout.println(`Earlier date: ${earlierDate}`)\n\n      // :=? sets a deadline only if none exists\n      deadline <- Date()\n      fallbackDate <- 2024-12-31\n      deadline :=? fallbackDate\n      stdout.println(`Fallback deadline: ${deadline}`)","migrationContext":"Java conflates these: Math.min(a, b) for comparison vs if (x == null) x = default for assignment — both need null checks. EK9 separates them cleanly: <? for comparison, :=? for conditional assignment. Each handles unset values automatically.","keywords":[":=?","<?","assignment","coalescing","comparison","contrast","distinction","guarded"],"primaryTopics":["<? vs :=? distinction","coalescing comparison","guarded assignment"],"typicalErrors":[],"companions":[]}
{"id":1227,"category":"Operators and Expressions","question":"Show all four coalescing comparison operators on Integer values.","url":"https://ek9.io/qa/QA1227.html","alternatePhrasings":["What are all the coalescing operators in EK9 and how do they differ?","I need to see <?, >?, <=?, >=? side by side on the same type.","In Java there's no equivalent to coalescing operators — show the full EK9 family.","Demonstrate the complete set of coalescing comparison operators with Integer examples."],"answer":"EK9 has four coalescing comparison operators. All handle unset values gracefully.\n\n  minOf() as pure\n    -> left as Integer, right as Integer\n    <- rtn as Integer: left <? right     //lesser\n  maxOf() as pure\n    -> left as Integer, right as Integer\n    <- rtn as Integer: left >? right     //greater\n  minOrEqual() as pure\n    -> left as Integer, right as Integer\n    <- rtn as Integer: left <=? right    //lesser-or-equal\n  maxOrEqual() as pure\n    -> left as Integer, right as Integer\n    <- rtn as Integer: left >=? right    //greater-or-equal\n\nAll four are COMPARISON operators — they compare two values and return a result. They are NOT assignment operators. Do not confuse them with :=? (guarded assignment), which is a completely different operation.\n\nSee Q1226 for the distinction between <? and :=?. See Q1092 for min/max coalescing basics. ","ek9Example":"defines module qa.operators.allcoalescing\n\n  defines function\n\n    // <? — returns the LESSER of two values\n    minOf() as pure\n      ->\n        left as Integer\n        right as Integer\n      <- rtn as Integer: left <? right\n\n    // >? — returns the GREATER of two values\n    maxOf() as pure\n      ->\n        left as Integer\n        right as Integer\n      <- rtn as Integer: left >? right\n\n    // <=? — returns the lesser-or-equal of two values\n    minOrEqual() as pure\n      ->\n        left as Integer\n        right as Integer\n      <- rtn as Integer: left <=? right\n\n    // >=? — returns the greater-or-equal of two values\n    maxOrEqual() as pure\n      ->\n        left as Integer\n        right as Integer\n      <- rtn as Integer: left >=? right\n\n  defines program\n\n    AllCoalescingOperatorsDemo()\n      stdout <- Stdout()\n\n      // === ALL FOUR OPERATORS WITH BOTH VALUES SET ===\n\n      scoreA <- 78\n      scoreB <- 92\n\n      minResult <- minOf(scoreA, scoreB)\n      stdout.println(`<? minimum: ${minResult}`)\n\n      maxResult <- maxOf(scoreA, scoreB)\n      stdout.println(`>? maximum: ${maxResult}`)\n\n      lessOrEqualResult <- minOrEqual(scoreA, scoreB)\n      stdout.println(`<=? lesser-or-equal: ${lessOrEqualResult}`)\n\n      greaterOrEqualResult <- maxOrEqual(scoreA, scoreB)\n      stdout.println(`>=? greater-or-equal: ${greaterOrEqualResult}`)\n\n      // === WITH EQUAL VALUES ===\n\n      valueA <- 50\n      valueB <- 50\n\n      equalMin <- minOf(valueA, valueB)\n      stdout.println(`<? with equal values: ${equalMin}`)\n\n      equalMax <- maxOf(valueA, valueB)\n      stdout.println(`>? with equal values: ${equalMax}`)\n\n      // === WITH ONE UNSET — all return the set value ===\n\n      activeReading <- 65\n      offlineReading <- Integer()\n\n      minUnset <- minOf(activeReading, offlineReading)\n      stdout.println(`<? one unset: ${minUnset}`)\n\n      maxUnset <- maxOf(activeReading, offlineReading)\n      stdout.println(`>? one unset: ${maxUnset}`)\n\n      lessEqualWhenUnset <- minOrEqual(activeReading, offlineReading)\n      stdout.println(`<=? one unset: ${lessEqualWhenUnset}`)\n\n      greaterEqualWhenUnset <- maxOrEqual(activeReading, offlineReading)\n      stdout.println(`>=? one unset: ${greaterEqualWhenUnset}`)\n\n      // === WITH BOTH UNSET — all return unset ===\n\n      missingA <- Integer()\n      missingB <- Integer()\n\n      noneMin <- minOf(missingA, missingB)\n      if noneMin?\n        stdout.println(`Unexpected: ${noneMin}`)\n      else\n        stdout.println(\"<? both unset: result is unset\")","migrationContext":"Java: requires 4 separate utility methods with null checks. Python: min/max handle None only with filtering. Kotlin: requires custom extension functions. EK9: <?, >?, <=?, >=? — four built-in operators that handle unset values automatically.","keywords":["<=?","<?",">=?",">?","all","coalescing","comparison","family","integer"],"primaryTopics":["coalescing operator family","<?",">?","<=?",">=?"],"typicalErrors":[],"companions":[]}
{"id":1228,"category":"Operators and Expressions","question":"Find the cheaper Product from two optional product records.","url":"https://ek9.io/qa/QA1228.html","alternatePhrasings":["How do I use <? coalescing on a custom record type?","Given two optional Product records, pick the one with the lower natural ordering.","In Java I'd use Comparator.comparing(Product::getPrice). How does EK9 handle this?","Migrating from Kotlin where I use minOf(a, b) for data classes — what does EK9 offer?"],"answer":"Define 'default operator' on the record to auto-generate <=> from fields (compared in declaration order). Then use <? in a pure function to pick the lesser product.\n\n  cheaperProduct() as pure\n    -> left as Product, right as Product\n    <- rtn as Product: left <? right\n\nThe <? coalescing minimum operator works on ANY type that has <=> defined — not just built-in types. If both values are set, <? returns the lesser. If one is unset, it returns the set one. If both are unset, the result is unset.\n\nSee Q1049 for comparison operators on records. See Q1208 for <? on built-in Date type.","ek9Example":"defines module qa.operators.coalescemin.record\n\n  defines record\n\n    Product\n      name as String: String()\n      price as Float: 0.0\n\n      Product()\n        ->\n          name as String\n          price as Float\n        this.name: name\n        this.price: price\n\n      default operator\n\n  defines function\n\n    cheaperProduct() as pure\n      ->\n        left as Product\n        right as Product\n      <- rtn as Product: left <? right\n\n  defines program\n\n    CoalesceMinRecordDemo()\n      stdout <- Stdout()\n\n      // === BOTH SET — returns the lesser by natural ordering ===\n\n      widget <- Product(\"Widget\", 19.99)\n      gadget <- Product(\"Gadget\", 29.99)\n      cheaper <- cheaperProduct(widget, gadget)\n      stdout.println(`Cheaper product: ${cheaper}`)\n\n      // === ONE UNSET — returns the set one ===\n\n      knownProduct <- Product(\"Keyboard\", 49.99)\n      unknownProduct <- Product()\n      available <- cheaperProduct(knownProduct, unknownProduct)\n      stdout.println(`Available product: ${available}`)\n\n      // === BOTH UNSET — result is unset ===\n\n      missingA <- Product()\n      missingB <- Product()\n      noProduct <- cheaperProduct(missingA, missingB)\n      if noProduct?\n        stdout.println(`Product: ${noProduct}`)\n      else\n        stdout.println(\"No product available\")\n\n      // === INLINE USAGE ===\n\n      monitor <- Product(\"Monitor\", 299.99)\n      mouse <- Product(\"Mouse\", 12.99)\n      bestDeal <- monitor <? mouse\n      stdout.println(`Best deal: ${bestDeal}`)","migrationContext":"Java: Stream.of(a, b).filter(Objects::nonNull).min(Comparator.naturalOrder()). Python: min(a, b, key=lambda p: (p.name, p.price)). Kotlin: minOf(a, b). EK9: left <? right — one operator, handles unset automatically.","keywords":["<?","cheaper","coalescing","default operator","minimum","product","record","user-defined"],"primaryTopics":["<? coalescing on records","default operator enables coalescing","user-defined type comparison"],"typicalErrors":[{"error":"E07235","correct":"      default operator","incorrect":"      //no operator defined","explanation":"The <? operator requires <=> to be defined. Use 'default operator' on the record to auto-generate it. See ek9 -h E15200 for details."}],"companions":[]}
{"id":1229,"category":"Operators and Expressions","question":"Select the higher-priority Task from two optional task records.","url":"https://ek9.io/qa/QA1229.html","alternatePhrasings":["How do I pick the task with the higher priority using coalescing operators?","Given two optional Task records with priority numbers, choose the more urgent one.","In Java I'd use Comparator.comparing(Task::getPriority) to find min priority. What does EK9 use?","Migrating from Python where I use min(tasks, key=lambda t: t.priority) — what is the EK9 equivalent?"],"answer":"Define a custom <=> operator that compares only the priority field. Lower priority number means higher priority, so use <? to pick the more urgent task.\n\n  higherPriority() as pure\n    -> left as Task, right as Task\n    <- rtn as Task: left <? right\n\nThe custom <=> lets you control which field determines ordering. The <? coalescing operator uses <=> to find the lesser value — and since lower priority number means higher urgency, <? picks the more urgent task.\n\nSee Q1228 for <? with default operator. See Q1049 for custom comparison operators.","ek9Example":"defines module qa.operators.coalescemax.record\n\n  defines record\n\n    Task\n      title as String: String()\n      priority as Integer: 0\n\n      Task()\n        ->\n          title as String\n          priority as Integer\n        this.title: title\n        this.priority: priority\n\n      operator <=> as pure\n        -> other as Task\n        <- rtn as Integer: priority <=> other.priority\n\n      default operator\n\n  defines function\n\n    higherPriority() as pure\n      ->\n        left as Task\n        right as Task\n      <- rtn as Task: left <? right\n\n  defines program\n\n    CoalesceMaxRecordDemo()\n      stdout <- Stdout()\n\n      // === BOTH SET — <? returns the lesser priority number (higher urgency) ===\n\n      criticalTask <- Task(\"Fix outage\", 1)\n      normalTask <- Task(\"Update docs\", 5)\n      moreUrgent <- higherPriority(criticalTask, normalTask)\n      stdout.println(`More urgent: ${moreUrgent}`)\n\n      // === ONE UNSET — returns the set one ===\n\n      knownTask <- Task(\"Deploy release\", 3)\n      unknownTask <- Task()\n      available <- higherPriority(knownTask, unknownTask)\n      stdout.println(`Available task: ${available}`)\n\n      // === BOTH UNSET — result is unset ===\n\n      pendingA <- Task()\n      pendingB <- Task()\n      noTask <- higherPriority(pendingA, pendingB)\n      if noTask?\n        stdout.println(`Task: ${noTask}`)\n      else\n        stdout.println(\"No task available\")\n\n      // === USE >? FOR LOWEST PRIORITY (largest number) ===\n\n      lowest <- criticalTask >? normalTask\n      stdout.println(`Lowest priority: ${lowest}`)","migrationContext":"Java: Collections.min(tasks, Comparator.comparingInt(Task::getPriority)). Python: min(tasks, key=lambda t: t.priority). Kotlin: tasks.minByOrNull { it.priority }. EK9: left <? right with custom <=> on priority field.","keywords":["<?","coalescing","comparison","custom operator","minimum","priority","record","task"],"primaryTopics":["<? with custom <=>","priority comparison","custom operator ordering"],"typicalErrors":[{"error":"E07550","correct":"      operator <=> as pure\n        -> other as Task\n        <- rtn as Integer: priority <=> other.priority","incorrect":"      operator <=> as pure\n        -> other as Task\n        <- rtn as Boolean: priority < other.priority","explanation":"The <=> operator must return Integer, not Boolean. It returns negative, zero, or positive to indicate ordering. See ek9 -h E07550 for details."}],"companions":[]}
{"id":1230,"category":"Operators and Expressions","question":"Sort two Measurement records by value and pick the smaller using <? coalescing.","url":"https://ek9.io/qa/QA1230.html","alternatePhrasings":["How do I coalesce custom records that compare by a specific field?","Given two sensor measurements, select the one with the lower reading.","In Java I'd use Comparator.comparingDouble for a single field. What does EK9 offer?","Migrating from Rust where I derive Ord on a struct — how does EK9 do custom ordering?"],"answer":"Define a custom <=> operator that compares only the reading field. The <? coalescing operator then uses that ordering.\n\n  operator <=> as pure\n    -> other as Measurement\n    <- rtn as Integer: reading <=> other.reading\n\n  smallerReading() as pure\n    -> left as Measurement, right as Measurement\n    <- rtn as Measurement: left <? right\n\nThe custom <=> means <? compares by reading only, ignoring label. This is different from 'default operator' which would compare label first (alphabetically), then reading.\n\nSee Q1228 for default operator ordering. See Q1229 for priority-based custom ordering.","ek9Example":"defines module qa.operators.coalesce.customcmp\n\n  defines record\n\n    Measurement\n      label as String: String()\n      reading as Float: 0.0\n\n      Measurement()\n        ->\n          label as String\n          reading as Float\n        this.label: label\n        this.reading: reading\n\n      operator <=> as pure\n        -> other as Measurement\n        <- rtn as Integer: reading <=> other.reading\n\n      default operator\n\n  defines function\n\n    smallerReading() as pure\n      ->\n        left as Measurement\n        right as Measurement\n      <- rtn as Measurement: left <? right\n\n  defines program\n\n    CoalesceCustomCmpDemo()\n      stdout <- Stdout()\n\n      // === BOTH SET — compares by reading only ===\n\n      sensorA <- Measurement(\"Station-North\", 22.5)\n      sensorB <- Measurement(\"Station-South\", 18.3)\n      smaller <- smallerReading(sensorA, sensorB)\n      stdout.println(`Smaller reading: ${smaller}`)\n\n      // === ONE UNSET — returns the set one ===\n\n      calibrated <- Measurement(\"Lab\", 15.0)\n      uncalibrated <- Measurement()\n      usable <- smallerReading(calibrated, uncalibrated)\n      stdout.println(`Usable reading: ${usable}`)\n\n      // === BOTH UNSET — result is unset ===\n\n      offlineA <- Measurement()\n      offlineB <- Measurement()\n      noReading <- smallerReading(offlineA, offlineB)\n      if noReading?\n        stdout.println(`Reading: ${noReading}`)\n      else\n        stdout.println(\"No reading available\")\n\n      // === >? FOR LARGER READING ===\n\n      larger <- sensorA >? sensorB\n      stdout.println(`Larger reading: ${larger}`)","migrationContext":"Java: Comparator.comparingDouble(Measurement::getReading). Python: min(a, b, key=lambda m: m.reading). Rust: impl Ord comparing self.reading. EK9: custom <=> on reading field, then left <? right.","keywords":["<?","coalescing","comparison","custom","measurement","minimum","reading","record"],"primaryTopics":["custom <=> for specific field","<? with custom ordering","measurement comparison"],"typicalErrors":[],"companions":[]}
{"id":1231,"category":"Operators and Expressions","question":"Find the nearest Waypoint from two optional locations using <? on a class with distance.","url":"https://ek9.io/qa/QA1231.html","alternatePhrasings":["How do I use <? coalescing on a class instead of a record?","Given two optional Waypoint objects with distance fields, pick the nearer one.","In Java I'd implement Comparable on a class to use min(). What does EK9 use?","Migrating from Python where I use min(wp1, wp2, key=lambda w: w.distance) — what is the EK9 pattern?"],"answer":"Define a class with private fields, accessor methods, a custom <=> operator comparing distance, and 'default operator' last. Then use <? in a pure function.\n\n  nearerWaypoint() as pure\n    -> left as Waypoint, right as Waypoint\n    <- rtn as Waypoint: left <? right\n\nClasses are used instead of records when you need behaviour (methods). Waypoint has name() and distance() accessor methods because class fields are always private. The <? operator works identically on classes and records — it only requires <=> to be defined.\n\nSee Q1228 for <? on records. ","ek9Example":"defines module qa.operators.coalesce.classmethod\n\n  defines class\n\n    Waypoint\n      waypointName <- String()\n      waypointDistance <- Float()\n\n      Waypoint()\n        ->\n          name as String\n          distance as Float\n        waypointName :=: name\n        waypointDistance :=: distance\n\n      name() as pure\n        <- rtn as String: waypointName\n\n      distance() as pure\n        <- rtn as Float: waypointDistance\n\n      operator <=> as pure\n        -> other as Waypoint\n        <- rtn as Integer: waypointDistance <=> other.waypointDistance\n\n      default operator\n\n  defines function\n\n    nearerWaypoint() as pure\n      ->\n        left as Waypoint\n        right as Waypoint\n      <- rtn as Waypoint: left <? right\n\n  defines program\n\n    CoalesceClassMethodDemo()\n      stdout <- Stdout()\n\n      // === BOTH SET — compares by distance ===\n\n      basecamp <- Waypoint(\"Basecamp\", 2.5)\n      summit <- Waypoint(\"Summit\", 8.7)\n      nearer <- nearerWaypoint(basecamp, summit)\n      stdout.println(`Nearer waypoint: ${nearer}`)\n\n      // === ONE UNSET — returns the set one ===\n\n      knownPoint <- Waypoint(\"Checkpoint\", 5.0)\n      unknownPoint <- Waypoint()\n      available <- nearerWaypoint(knownPoint, unknownPoint)\n      stdout.println(`Available waypoint: ${available}`)\n\n      // === BOTH UNSET — result is unset ===\n\n      lostA <- Waypoint()\n      lostB <- Waypoint()\n      noWaypoint <- nearerWaypoint(lostA, lostB)\n      if noWaypoint?\n        stdout.println(`Waypoint: ${noWaypoint}`)\n      else\n        stdout.println(\"No waypoint available\")\n\n      // === >? FOR FARTHEST ===\n\n      farthest <- basecamp >? summit\n      stdout.println(`Farthest waypoint: ${farthest}`)","migrationContext":"Java: class Waypoint implements Comparable<Waypoint> with compareTo(). Python: class with __lt__ and min(). Kotlin: class with compareTo(). EK9: class with <=> operator, then left <? right.","keywords":["<?","accessor","class","coalescing","distance","method","minimum","waypoint"],"primaryTopics":["<? coalescing on classes","class vs record for coalescing","accessor methods"],"typicalErrors":[{"error":"E07290","correct":"      name() as pure\n        <- rtn as String: waypointName","incorrect":"      name as String: String()","explanation":"Class fields are always private. To expose state, define accessor methods. Public fields are only allowed on records. See ek9 -h E07290 for details."}],"companions":[]}
{"id":1232,"category":"Operators and Expressions","question":"Apply a default Configuration record only if no configuration was provided.","url":"https://ek9.io/qa/QA1232.html","alternatePhrasings":["How do I use :=? guarded assignment with a record type?","Given an optional Configuration record, apply defaults if it is unset.","In Java I'd check if config == null before setting defaults. What does EK9 offer?","Migrating from Kotlin where I use config ?: defaultConfig — what is the EK9 equivalent?"],"answer":"The :=? guarded assignment operator works on ANY type, not just primitives. It assigns the value only if the target variable is currently unset.\n\n  config <- Configuration()\n  defaultConfig <- Configuration(\"localhost\", 8080)\n  config :=? defaultConfig\n\nIf config is unset, it receives the default. If config already has a value, the assignment is skipped. This is an ASSIGNMENT operator.\n\nSee Q1220 for :=? on strings. ","ek9Example":"defines module qa.operators.guardassign.record\n\n  defines record\n\n    Configuration\n      host as String: String()\n      port as Integer: 0\n\n      Configuration()\n        ->\n          host as String\n          port as Integer\n        this.host: host\n        this.port: port\n\n      default operator\n\n  defines function\n\n    loadSavedConfig()\n      <- rtn <- Configuration()\n      //Simulates: no saved configuration found\n\n    loadEnvironmentConfig()\n      <- rtn <- Configuration(\"env-host\", 9090)\n\n  defines program\n\n    GuardAssignRecordDemo()\n      stdout <- Stdout()\n\n      // === RECORD IS UNSET — :=? assigns the default ===\n\n      config <- Configuration()\n      defaultPort <- 8080\n      defaultConfig <- Configuration(\"localhost\", defaultPort)\n      config :=? defaultConfig\n      stdout.println(`Config: ${config}`)\n\n      // === RECORD IS ALREADY SET — :=? is skipped ===\n\n      existingConfig <- Configuration(\"production.example.com\", 443)\n      existingConfig :=? defaultConfig\n      stdout.println(`Existing preserved: ${existingConfig}`)\n\n      // === FALLBACK CHAIN — first set value wins ===\n\n      appConfig <- Configuration()\n      appConfig :=? loadSavedConfig()\n      appConfig :=? loadEnvironmentConfig()\n      fallbackPort <- 3000\n      appConfig :=? Configuration(\"fallback-host\", fallbackPort)\n      stdout.println(`App config: ${appConfig}`)","migrationContext":"Java: if (config == null) config = defaultConfig. Python: config = config or defaultConfig. Kotlin: config = config ?: defaultConfig. EK9: config :=? defaultConfig — one operator, works on any type.","keywords":[":=?","assignment","configuration","default","guarded","record","unset","user-defined"],"primaryTopics":[":=? on user-defined records","guarded assignment beyond primitives","configuration defaults"],"typicalErrors":[{"error":"E01073","correct":"      config :=? defaultConfig","incorrect":"      if config == null\n        config := defaultConfig","explanation":"EK9 has no null. Use :=? to conditionally assign when a variable is unset. This works on records, classes, and all types. See ek9 -h E01072 for details."}],"companions":[]}
{"id":1233,"category":"Streams and Pipelines","question":"Process a list of optional sensor readings and select the minimum using stream join with <?.","url":"https://ek9.io/qa/QA1233.html","alternatePhrasings":["How do I reduce a stream of custom records to find the minimum?","Given sensor readings from multiple stations, find the coolest using a pipeline.","In Java I'd use stream().min(Comparator.comparing(SensorReading::getTemperature)). What does EK9 use?","Migrating from Python where I use min(readings, key=lambda r: r.temperature) — how does EK9 do this?"],"answer":"Define a pure function using <? and pass it to 'join with' in a stream pipeline. The join operation reduces the stream to a single value.\n\n  coolerOf() as pure\n    -> left as SensorReading, right as SensorReading\n    <- rtn as SensorReading: left <? right\n\n  coolest <- cat readings | join with coolerOf | collect as SensorReading\n\nThe <? operator handles unset values gracefully within the stream. The custom <=> on temperature determines ordering.\n\nSee Q1139 for join basics. See Q1230 for custom <=> on records.","ek9Example":"defines module qa.streams.coalesce.pipeline\n\n  defines record\n\n    SensorReading\n      stationId as String: String()\n      temperature as Float: 0.0\n\n      SensorReading()\n        ->\n          stationId as String\n          temperature as Float\n        this.stationId: stationId\n        this.temperature: temperature\n\n      operator <=> as pure\n        -> other as SensorReading\n        <- rtn as Integer: temperature <=> other.temperature\n\n      default operator\n\n  defines function\n\n    coolerOf() as pure\n      ->\n        left as SensorReading\n        right as SensorReading\n      <- rtn as SensorReading: left <? right\n\n    warmerOf() as pure\n      ->\n        left as SensorReading\n        right as SensorReading\n      <- rtn as SensorReading: left >? right\n\n  defines program\n\n    CoalesceInPipelineDemo()\n      stdout <- Stdout()\n\n      // === BUILD A LIST OF SENSOR READINGS ===\n\n      readings <- List() of SensorReading\n      readings += SensorReading(\"North\", 22.5)\n      readings += SensorReading(\"South\", 18.3)\n      readings += SensorReading(\"East\", 25.1)\n      readings += SensorReading(\"West\", 15.8)\n\n      // === FIND COOLEST USING STREAM JOIN WITH <? ===\n\n      coolestList <- cat readings | join with coolerOf | collect as List of SensorReading\n      stdout.println(`Coolest: ${coolestList}`)\n\n      // === FIND WARMEST USING STREAM JOIN WITH >? ===\n\n      warmestList <- cat readings | join with warmerOf | collect as List of SensorReading\n      stdout.println(`Warmest: ${warmestList}`)\n\n      // === SORTED OUTPUT ===\n\n      stdout.println(\"All readings sorted by temperature:\")\n      cat readings | sort > stdout","migrationContext":"Java: stream().min(Comparator.comparingDouble(SensorReading::getTemperature)). Python: min(readings, key=lambda r: r.temperature). Rust: readings.iter().min_by_key(|r| r.temperature). EK9: cat readings | join with coolerOf | collect as SensorReading.","keywords":["<?","coalescing","join","minimum","pipeline","record","reduce","sensor","stream"],"primaryTopics":["<? in stream pipelines","join with coalescing function","stream reduction on records"],"typicalErrors":[{"error":"E50060","correct":"coolestList <- cat readings | join with coolerOf | collect as List of SensorReading","incorrect":"coolestList <- readings.stream().min()","explanation":"EK9 List has no .stream() method; reduce with a stream pipeline 'cat list | join with fn'. See ek9 -h E50060 for details."}],"companions":[]}
{"id":1234,"category":"Generics","question":"Create me a generic Processor class of type T with an abstract process method, then extend it inline using a dynamic class with String as the type parameter.","url":"https://ek9.io/qa/QA1234.html","alternatePhrasings":["Write a generic abstract class with one typed method and provide a dynamic class implementation.","Show me how to extend a generic abstract class inline with a concrete type using a dynamic class.","Implement a generic Processor of T that I can extend at the use site for a specific type.","Create a parameterised abstract class and instantiate it for String."],"answer":"Generic abstract classes in EK9 use 'of type T' to declare a type parameter. They can be extended INLINE in expression position using a dynamic class with the syntax '() extends ParentName of ConcreteType as class'. The dynamic class overrides the abstract method, with the type parameter T replaced by the concrete type.\n\nGENERIC ABSTRACT CLASS\n  Processor of type T as abstract\n    process() as abstract\n      -> arg0 as T\n      <- rtn as String?\n    override operator ? as pure\n      <- rtn as Boolean: true\n\nDYNAMIC CLASS EXTENSION\nThe dynamic class is created at the call site:\n  instance <- () extends Processor of String as class\n    override process()\n      -> arg0 as String\n      <- rtn as String: \"Processed: \" + arg0\n\nUSAGE\n  result <- instance.process(\"Hello\")\n  stdout.println(result)\n\nThe '() extends Processor of String as class' form parameterises the parent generic with a concrete type AND creates an instance in one expression. This is more concise than declaring a named subclass when only one usage is needed.\n\nSee Q642 for generic constructor inference. See Q649 for generic function implementation. See Q194 for generic class basics.","ek9Example":"defines module qa.genericsdeep.processorbasic\n\n  defines class\n\n    Processor of type T as abstract\n\n      process() as abstract\n        -> arg0 as T\n        <- rtn as String?\n\n      override operator ? as pure\n        <- rtn as Boolean: true\n\n  defines program\n\n    ProcessorDemo()\n      stdout <- Stdout()\n\n      instance <- () extends Processor of String as class\n        override process()\n          -> arg0 as String\n          <- rtn as String: \"Processed: \" + arg0\n\n      result <- instance.process(\"Hello\")\n      stdout.println(result)","migrationContext":"Java: anonymous inner class extending Processor<String> { @Override public String process(String arg) { ... } }. Kotlin: object : Processor<String>() { override fun process(arg: String) = ... }. Scala: new Processor[String] { override def process(arg: String) = ... }. EK9: '() extends Processor of String as class' is the dynamic class form for inline parameterised extension.","keywords":["T","abstract","dynamic class","extends","generic","inline","of type","type parameter"],"primaryTopics":["generic class extension","dynamic class with generic","of type T"],"typicalErrors":[{"error":"E50040","correct":"Processor of type T as abstract","incorrect":"Processor of type T as open","explanation":"An abstract generic with abstract methods must be declared 'as abstract'. Using 'as open' implies the methods have bodies that may be overridden, which leads to unresolved type issues when the dynamic class tries to extend it. See ek9 -h E50040 for details."}],"companions":[]}
{"id":1235,"category":"Generics","question":"Write me a generic Formatter of type T that captures a prefix variable from the enclosing scope and uses it inside the overridden format method.","url":"https://ek9.io/qa/QA1235.html","alternatePhrasings":["Create a dynamic class extending a generic type that captures a local variable.","Show me how generic dynamic classes combine with variable capture in EK9.","Implement a parameterised Formatter that captures a prefix and formats Integer values.","Build a generic formatter dynamic class that uses an outer-scope variable."],"answer":"Dynamic classes that extend a generic type can ALSO capture variables from the enclosing scope. The captured variables are listed in the parentheses before 'extends', and become fields on the dynamic class. They can be used inside the overridden methods alongside the type-substituted parameters.\n\nGENERIC ABSTRACT FORMATTER\n  Formatter of type T as abstract\n    format() as abstract\n      -> arg0 as T\n      <- rtn as String?\n    override operator ? as pure\n      <- rtn as Boolean: true\n\nDYNAMIC CLASS WITH CAPTURE\n  prefix <- \"Item\"\n  formatter <- (prefix) extends Formatter of Integer as class\n    override format()\n      -> arg0 as Integer\n      <- rtn as String: `${prefix}: ${$arg0}`\n    default operator ?\n\nThe parentheses '(prefix)' declare which outer variables to capture. They become fields on the anonymous class and are visible inside the overridden methods. The generic type parameter T is replaced by Integer at the same time.\n\nUSAGE\n  stdout.println(formatter.format(42))\n\nThis combines TWO features in one expression: generic type extension AND closure-style capture. The captured prefix coexists with the substituted Integer parameter.\n\nSee Q1234 for basic generic extension without capture. See Q601 for dynamic functions with capture. See Q197 for extending generics.","ek9Example":"defines module qa.genericsdeep.formattercapture\n\n  defines class\n\n    Formatter of type T as abstract\n\n      format() as abstract\n        -> arg0 as T\n        <- rtn as String?\n\n      override operator ? as pure\n        <- rtn as Boolean: true\n\n  defines program\n\n    FormatterCaptureDemo()\n      stdout <- Stdout()\n      prefix <- \"Item\"\n\n      formatter <- (prefix) extends Formatter of Integer as class\n        override format()\n          -> arg0 as Integer\n          <- rtn as String: `${prefix}: ${$arg0}`\n        default operator ?\n\n      stdout.println(formatter.format(42))","migrationContext":"Java: anonymous inner class capturing effectively-final outer variables. Kotlin: object expression with closure over outer scope. Scala: anonymous subclass with closure. JavaScript: class extending a generic-like base that closes over outer variables. EK9: explicit '(capturedVar)' syntax makes capture intentional and visible.","keywords":["anonymous","capture","closure","dynamic class","extends","generic","inline","of type"],"primaryTopics":["generic dynamic class with capture","variable capture","anonymous extension"],"typicalErrors":[{"error":"E50001","correct":"(prefix) extends Formatter of Integer as class","incorrect":"() extends Formatter of Integer as class","explanation":"If you reference an outer-scope variable inside the dynamic class body but do not list it in the capture parentheses, the variable is not resolved (E50001) because it is never brought into the dynamic class's scope. List it in the capture parentheses, e.g. (prefix), to make it available. See ek9 -h E50001 for details."}],"companions":[]}
{"id":1236,"category":"Dispatcher Validation","question":"Create me a dispatcher method for a sealed Shape trait that has Circle, Square and Triangle handlers, demonstrating exhaustive runtime dispatch.","url":"https://ek9.io/qa/QA1236.html","alternatePhrasings":["Write a dispatcher with handlers for every permitted type of a sealed trait.","Implement exhaustive dispatch over a sealed Shape hierarchy.","Show me how to use a dispatcher with 'allow only' to enforce exhaustive handling.","Build a ShapeProcessor that dispatches differently for Circle, Square and Triangle."],"answer":"A dispatcher method declared with 'as dispatcher' resolves at runtime to the most specific overload based on the actual parameter type. When the parameter type is a sealed trait declared with 'allow only', you can write one handler per permitted type and the compiler ensures exhaustive coverage.\n\nSEALED TRAIT\n  Shape allow only Circle, Square, Triangle\n    name() as abstract\n      <- rtn as String?\n\nIMPLEMENTING CLASSES\n  Circle with trait of Shape\n    override name()\n      <- rtn as String: \"Circle\"\n\n  Square with trait of Shape\n    override name()\n      <- rtn as String: \"Square\"\n\n  Triangle with trait of Shape\n    override name()\n      <- rtn as String: \"Triangle\"\n\nDISPATCHER ENTRY AND HANDLERS\nThe entry takes the trait type; each handler takes a permitted concrete type:\n  ShapeProcessor\n    describe() as dispatcher\n      -> shape as Shape\n      <- rtn as String: \"Unknown\"\n\n    describe()\n      -> shape as Circle\n      <- rtn as String: \"Handled Circle\"\n\n    describe()\n      -> shape as Square\n      <- rtn as String: \"Handled Square\"\n\n    describe()\n      -> shape as Triangle\n      <- rtn as String: \"Handled Triangle\"\n\nUSAGE\n  processor <- ShapeProcessor()\n  stdout.println(processor.describe(Circle()))\n  stdout.println(processor.describe(Square()))\n  stdout.println(processor.describe(Triangle()))\n\nEach call selects the handler whose parameter most closely matches the runtime type. The 'allow only' clause means the compiler can verify ALL permitted types have a handler.\n\nSee Q618 for sealed exhaustive dispatch rules. See Q612 for purity matching. See Q60 for dispatcher fundamentals.","ek9Example":"defines module qa.dispatcher.sealedshape\n\n  defines trait\n\n    Shape allow only Circle, Square, Triangle\n      name() as abstract\n        <- rtn as String?\n\n  defines class\n\n    Circle with trait of Shape\n      override name()\n        <- rtn as String: \"Circle\"\n\n    Square with trait of Shape\n      override name()\n        <- rtn as String: \"Square\"\n\n    Triangle with trait of Shape\n      override name()\n        <- rtn as String: \"Triangle\"\n\n    ShapeProcessor\n\n      describe() as dispatcher\n        -> shape as Shape\n        <- rtn as String: \"Unknown\"\n\n      describe()\n        -> shape as Circle\n        <- rtn as String: \"Handled Circle\"\n\n      describe()\n        -> shape as Square\n        <- rtn as String: \"Handled Square\"\n\n      describe()\n        -> shape as Triangle\n        <- rtn as String: \"Handled Triangle\"\n\n  defines program\n\n    SealedShapeDispatchDemo()\n      stdout <- Stdout()\n      processor <- ShapeProcessor()\n\n      stdout.println(processor.describe(Circle()))\n      stdout.println(processor.describe(Square()))\n      stdout.println(processor.describe(Triangle()))","migrationContext":"Java: visitor pattern with accept(visitor) on each subclass. Scala: pattern matching on sealed trait. Kotlin: when expression on sealed class. Rust: match on enum variants. EK9: dispatcher method with one handler per permitted type — no boilerplate, type-checked, exhaustiveness verified.","keywords":["allow only","dispatch","dispatcher","exhaustive","handler","runtime","sealed","trait"],"primaryTopics":["sealed trait dispatcher","exhaustive dispatch","allow only"],"typicalErrors":[{"error":"E07120","correct":"describe() as dispatcher\n        -> shape as Shape","incorrect":"describe() as pure dispatcher\n        -> shape as Shape\n      describe()\n        -> shape as Circle","explanation":"The dispatcher entry and all handlers must agree on purity. If the entry is 'as pure dispatcher', every handler must also be 'as pure'. The compiler detects this as a method signature conflict. See ek9 -h E07120 for details."}],"companions":[]}
{"id":1237,"category":"Security and Sanitization","question":"Write me a pure function that takes a sanitized String parameter and returns a different status string for safe input versus blocked malicious input.","url":"https://ek9.io/qa/QA1237.html","alternatePhrasings":["Create a function with a sanitized String parameter and demonstrate it on SQL injection and XSS inputs.","Show me how the sanitized keyword works on a function parameter in EK9.","Implement an input-checking function that uses the sanitized modifier.","Build a processInput function that rejects malicious payloads."],"answer":"The 'sanitized' modifier on a parameter declares that the value is treated as untrusted external input and must pass the sanitization pipeline before the function body sees it. Inside the function, an unset value indicates the input was rejected and a set value indicates the input is safe to use.\n\nFUNCTION WITH SANITIZED PARAMETER\n  processInput()\n    -> input as sanitized String\n    <- result as String: \"unset\"\n    if input?\n      result: \"safe\"\n    else\n      result: \"blocked\"\n\nThe 'sanitized' keyword is part of the parameter declaration. The compiler enforces that the function only sees sanitized values — malicious inputs like SQL injection or XSS payloads result in an unset parameter, NOT a thrown exception.\n\nDEMONSTRATING SAFE AND BLOCKED INPUTS\n  // Safe input — passes through\n  result1 <- processInput(\"Hello World\")\n  stdout.println(\"Safe: \" + result1)        // \"Safe: safe\"\n\n  // SQL injection — blocked\n  result2 <- processInput(\"'; DROP TABLE users--\")\n  stdout.println(\"SQL: \" + result2)         // \"SQL: blocked\"\n\n  // XSS — blocked\n  result3 <- processInput(\"<script>alert('xss')</script>\")\n  stdout.println(\"XSS: \" + result3)         // \"XSS: blocked\"\n\nKEY POINT\nThe function does not check for malicious patterns itself. The 'sanitized' modifier is the contract — the compiler and runtime ensure the parameter is either safe or unset. The function body simply checks 'input?' to know which case it is in.\n\nSee Q215 for sanitized parameters basics. See Q269 for input validation patterns. See Q272 for defense in depth.","ek9Example":"defines module qa.security.sanitizedfunction\n\n  defines function\n\n    processInput()\n      -> input as sanitized String\n      <- result as String: \"unset\"\n      if input?\n        result: \"safe\"\n      else\n        result: \"blocked\"\n\n  defines program\n\n    SanitizedFunctionDemo()\n      stdout <- Stdout()\n\n      stdout.println(\"=== Sanitized Function Param Demo ===\")\n\n      safe <- \"Hello World\"\n      result1 <- processInput(safe)\n      stdout.println(\"Safe: \" + result1)\n\n      sqlInject <- \"'; DROP TABLE users--\"\n      result2 <- processInput(sqlInject)\n      stdout.println(\"SQL: \" + result2)\n\n      xss <- \"<script>alert('xss')</script>\"\n      result3 <- processInput(xss)\n      stdout.println(\"XSS: \" + result3)\n\n      safeName <- \"John O'Brien\"\n      result4 <- processInput(safeName)\n      stdout.println(\"Name: \" + result4)\n\n      stdout.println(\"=== Complete ===\")","migrationContext":"Java: manual validation with OWASP ESAPI or Bean Validation annotations. Python: html.escape and re.sub patterns or bleach library. Rust: newtype pattern with manual validation in the constructor. Go: explicit if-err checks after every input. EK9: 'sanitized' modifier delegates input validation to the runtime; the function only sees safe values.","keywords":["SQL injection","XSS","function","input","parameter","sanitized","security","untrusted","validation"],"primaryTopics":["sanitized parameter","input sanitization","function security"],"typicalErrors":[{"error":"E50001","correct":"-> input as sanitized String","incorrect":"-> input as String sanitized","explanation":"The 'sanitized' modifier comes BEFORE the type, not after. The correct form is 'input as sanitized String'. See ek9 -h E50001 for details."}],"companions":[]}
{"id":1238,"category":"Design Patterns and Idioms","question":"Implement me a strategy pattern for applying different discount calculations to a price, using an abstract function and two dynamic function strategies.","url":"https://ek9.io/qa/QA1238.html","alternatePhrasings":["Create a Discounter strategy with two implementations and a function that applies the chosen strategy.","Show me how to swap discount algorithms at runtime using EK9 abstract functions.","Write a price calculator that takes a discount strategy as a parameter.","Build a strategy pattern example for ten-percent and twenty-percent discounts."],"answer":"EK9 implements the strategy pattern using an abstract function as the strategy interface and dynamic functions as concrete strategies. A separate function takes the value AND the strategy as parameters, calling the strategy through the function-call syntax.\n\nABSTRACT FUNCTION AS STRATEGY\n  Discounter() as pure abstract\n    -> price as Float\n    <- rtn as Float?\n\nDYNAMIC FUNCTION STRATEGIES\nEach concrete strategy is a dynamic function with the signature of the abstract:\n  tenPercent <- () is Discounter as pure function\n    rtn: price * 0.9\n\n  twentyPercent <- () is Discounter as pure function\n    rtn: price * 0.8\n\nThe '() is Discounter as pure function' form creates an instance of the abstract function with a concrete body. The body uses 'rtn:' to assign the return variable.\n\nPURE FUNCTION THAT TAKES A STRATEGY\nThe strategy is passed as a parameter typed by the abstract function:\n  applyDiscount() as pure\n    ->\n      price as Float\n      strategy as Discounter\n    <- rtn as Float: strategy(price)\n\nCalling 'strategy(price)' invokes whichever concrete strategy was passed in.\n\nUSAGE\n  full <- 100.0\n  cheaper <- applyDiscount(full, tenPercent)\n  cheapest <- applyDiscount(full, twentyPercent)\n  stdout.println(`Full: ${full}, 10% off: ${cheaper}, 20% off: ${cheapest}`)\n\nKEY ADVANTAGES\nNo class hierarchy needed. The abstract function defines the contract; dynamic functions provide implementations; the consumer just calls the parameter as a function. This is more concise than the Java/C# strategy interface + class implementations approach.\n\nSee Q214 for strategy pattern basics. See Q57 for strategy without subclassing. See Q52 for dynamic functions.","ek9Example":"defines module qa.patterns.discountstrategy\n\n  defines function\n\n    Discounter() as pure abstract\n      -> price as Float\n      <- rtn as Float?\n\n    applyDiscount() as pure\n      ->\n        price as Float\n        strategy as Discounter\n      <- rtn as Float: strategy(price)\n\n  defines program\n\n    DiscountStrategyDemo()\n      stdout <- Stdout()\n\n      tenPercent <- () is Discounter as pure function\n        rtn: price * 0.9\n\n      twentyPercent <- () is Discounter as pure function\n        rtn: price * 0.8\n\n      full <- 100.0\n      cheaper <- applyDiscount(full, tenPercent)\n      cheapest <- applyDiscount(full, twentyPercent)\n\n      stdout.println(`Full: ${full}, 10% off: ${cheaper}, 20% off: ${cheapest}`)","migrationContext":"Java: Strategy interface with implementing classes (DiscountStrategy interface, TenPercentDiscount implements). Python: pass functions directly (first-class). Kotlin: function types ((Double) -> Double) or interface implementations. Rust: trait objects (Box<dyn Discounter>) or closures. Go: function types. EK9: abstract function as the contract and dynamic functions as implementations — more concise than interface+class but with explicit type contract.","keywords":["abstract function","behaviour parameterisation","callback","discount","dynamic function","pattern","strategy","swap algorithm"],"primaryTopics":["strategy pattern","abstract function as interface","dynamic function strategy"],"typicalErrors":[{"error":"E07110","correct":"Discounter() as pure abstract","incorrect":"Discounter() as open","explanation":"An abstract function with no body must be declared 'as abstract' (or 'as pure abstract'). Using 'as open' is for functions WITH a body that can be overridden. See ek9 -h E07110 for details."}],"companions":[]}
{"id":1239,"category":"Control Flow Without break/continue/return","question":"Convert me a Java for-loop with break (find the first item over a threshold) into the equivalent EK9 stream pipeline.","url":"https://ek9.io/qa/QA1239.html","alternatePhrasings":["Migrate a Java for/break loop to EK9 without using break.","Show me how to replace 'for + break on first match' with an EK9 stream.","Rewrite a Java early-exit loop in idiomatic EK9.","How do I express 'find first over N' without a loop in EK9?"],"answer":"EK9 has no break statement, so loops with early exit on first match are expressed as a stream pipeline using filter + head. The pipeline is more declarative AND eliminates the loop variable that Java needed.\n\nTHE JAVA PATTERN\n  Integer firstBig = null;\n  for (Integer n : numbers) {\n    if (n > 50) {\n      firstBig = n;\n      break;\n    }\n  }\n  if (firstBig != null) System.out.println(firstBig);\n\nThree problems: a mutable accumulator, a break statement, and a null check. EK9 eliminates all three.\n\nTHE EK9 STREAM EQUIVALENT\nUse filter to keep only matching items, then head to take the first one, then collect into a List:\n  isBig() as pure\n    -> n as Integer\n    <- big as Boolean: n > 50\n\n  firstBig <- cat numbers | filter by isBig | head 1 | collect as List of Integer\n\nThe pipeline reads top-to-bottom: 'cat' streams the list, 'filter' keeps matches, 'head 1' takes the first, 'collect' produces a final List. There is no loop variable, no mutable accumulator, no break.\n\nUSAGE\n  numbers <- [10, 25, 60, 75, 90]\n  firstBig <- cat numbers | filter by isBig | head 1 | collect as List of Integer\n  if firstBig?\n    stdout.println(`First > 50: ${firstBig}`)\n\nWHY EK9 PREFERS THIS\n1. No break — loop control is implicit in the pipeline.\n2. No mutable accumulator — the result is an expression.\n3. The filter predicate is reusable and unit-testable on its own.\n4. Adding 'sort' or 'map' is one more pipe stage, not a refactor.\n\nSee Q284 for find-first via guarded assignment. See Q288 for migrating break loops. See Q142 for stream pipeline basics.","ek9Example":"defines module qa.without.streamfindfirst\n\n  defines constant\n\n    BIG_THRESHOLD <- 50\n\n  defines function\n\n    isBig() as pure\n      -> n as Integer\n      <- big as Boolean: n > BIG_THRESHOLD\n\n  defines program\n\n    MigrateBreakDemo()\n      stdout <- Stdout()\n\n      numbers <- [10, 25, 60, 75, 90]\n\n      firstBig <- cat numbers | filter by isBig | head 1 | collect as List of Integer\n\n      if firstBig?\n        stdout.println(`First > ${BIG_THRESHOLD}: ${firstBig}`)","migrationContext":"Java: for + break, or stream().filter().findFirst().orElse(null). Python: next((x for x in items if predicate(x)), None). Kotlin: items.firstOrNull { predicate(it) }. Rust: items.iter().find(|x| predicate(x)). Go: explicit for loop with break. EK9: 'cat items | filter by predicate | head 1 | collect as List of T' — explicit pipeline without break and without nullable return.","keywords":["Java to EK9","break","early exit","filter","find first","head","migration","stream","without break"],"primaryTopics":["replace break with stream","filter head pipeline","find first match"],"typicalErrors":[{"error":"E01070","correct":"firstBig <- cat numbers | filter by isBig | head 1 | collect as List of Integer","incorrect":"for n in numbers\n        if n > 50\n          firstBig: n\n          break","explanation":"EK9 has no 'break' statement. Use a stream pipeline with 'filter | head 1' to take only the first match, eliminating the need for early loop exit. See ek9 -h E01070 for details."}],"companions":[]}
{"id":1240,"category":"Data Flow Safety","question":"Write me a pure function categorise that takes an Integer score and returns a String category ('high', 'medium' or 'low'), proving the return variable is initialised on every branch.","url":"https://ek9.io/qa/QA1240.html","alternatePhrasings":["Create a function whose return value is provably set on every if/else branch.","Show me how to satisfy EK9 data flow analysis with branch initialisation.","Implement a categoriser that the compiler can prove always returns a value.","Write a function with a default-initialised return variable for safety."],"answer":"EK9 tracks the initialisation state of every variable through all control flow paths. A return variable can be made provably initialised in TWO ways: provide a default value at declaration time, or assign in EVERY branch of every conditional.\n\nPATTERN 1: DEFAULT VALUE AT DECLARATION (RECOMMENDED)\nThe return variable starts with a sensible default. Branches override it as needed:\n  categorise() as pure\n    -> score as Integer\n    <- rtn as String: \"low\"\n    if score >= 80\n      rtn: \"high\"\n    else if score >= 50\n      rtn: \"medium\"\n\nThe compiler accepts this because 'rtn' is initialised before any branch runs. The if/else only modifies an already-set value.\n\nPATTERN 2: EXPLICIT ASSIGNMENT ON EVERY BRANCH\nDeclare the return variable without a default but assign in every branch:\n  categorise() as pure\n    -> score as Integer\n    <- rtn as String?\n    if score >= 80\n      rtn: \"high\"\n    else if score >= 50\n      rtn: \"medium\"\n    else\n      rtn: \"low\"\n\nThe final 'else' is required — without it, a score below 50 would leave 'rtn' uninitialised and the compiler raises E08020.\n\nWHY DEFAULT IS PREFERRED\nPattern 1 is more robust: adding a new branch (e.g. 'if score >= 95 then \"excellent\"') does not risk leaving rtn uninitialised because the default still applies to any path that does not override it. Pattern 2 requires updating the else clause whenever cases change.\n\nUSAGE\n  stdout.println(categorise(95))   // \"high\"\n  stdout.println(categorise(70))   // \"medium\"\n  stdout.println(categorise(30))   // \"low\"\n\nSee Q633 for branch initialisation rules. See Q632 for definition order. See Q634 for guard-based safe access.","ek9Example":"defines module qa.dataflow.categorise\n\n  defines constant\n\n    HIGH_THRESHOLD <- 80\n\n    MEDIUM_THRESHOLD <- 50\n\n  defines function\n\n    categorise() as pure\n      -> score as Integer\n      <- rtn as String: \"low\"\n      if score >= HIGH_THRESHOLD\n        rtn: \"high\"\n      else if score >= MEDIUM_THRESHOLD\n        rtn: \"medium\"\n\n  defines program\n\n    CategoriseDemo()\n      stdout <- Stdout()\n\n      stdout.println(`Score 95: ${categorise(95)}`)\n      stdout.println(`Score 70: ${categorise(70)}`)\n      stdout.println(`Score 30: ${categorise(30)}`)","migrationContext":"Java: definite assignment analysis catches some of these but allows null. Rust: must assign on every path or use Option<T>. Go: zero-value initialisation always works but may hide bugs. Python: NameError at runtime. EK9: compile-time guarantee — either declare with default or assign on every branch, no third option.","keywords":["E08020","branch","data flow","default value","if else","initialisation","return variable","uninitialised"],"primaryTopics":["return variable initialisation","branch safe assignment","default declaration"],"typicalErrors":[{"error":"E08020","correct":"      <- rtn as String: \"low\"\n      if score >= HIGH_THRESHOLD\n        rtn: \"high\"\n      else if score >= MEDIUM_THRESHOLD\n        rtn: \"medium\"","incorrect":"<- rtn as String?\n      if score >= 80\n        rtn: \"high\"\n      else if score >= 50\n        rtn: \"medium\"","explanation":"Without a default value AND without a final else, rtn is uninitialised when score < 50. Either provide a default at declaration or add an else branch that assigns rtn. See ek9 -h E08020 for details."}],"companions":[]}
{"id":1241,"category":"Security and Sanitization","question":"Create me a constrained EmailAddress type as String validated by a regex, then write a registerUser function that uses a guard expression to accept only valid emails.","url":"https://ek9.io/qa/QA1241.html","alternatePhrasings":["Define an EmailAddress constrained type and use it with a guard.","Show me a validated email type that is unset on bad input.","Implement a constrained String type with regex matching for emails.","Write a registration function that rejects malformed email addresses."],"answer":"EK9 constrained types let you build domain types from existing primitive types with a validation rule. There are two construction paths. A BARE constructor — EmailAddress(value) — ASSERTS the value is valid: a set value that violates the regex PANICS at runtime (and an invalid literal constant is the compile error E08260, so it never reaches runtime). For UNTRUSTED/boundary input use the fallible factory EmailAddress().of(value), which returns an UNSET instance on failure — not an exception — and composes cleanly with guard expressions to filter bad data at the boundary.\n\nDEFINING THE CONSTRAINED TYPE\n  defines type\n    EmailAddress as String constrain as\n      matches /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$/\n\nThe type is declared in a 'defines type' section. The fallible factory (e.g. EmailAddress().of(\"alice@example.com\")) attempts to validate the regex; if it fails, the result is unset.\n\nFUNCTION USING THE TYPE\n  registerUser() as pure\n    ->\n      userName as String\n      email as EmailAddress\n    <- rtn as String: `Registered ${userName} with ${email}`\n\nThe function takes EmailAddress (not String). The type system makes it impossible to call this function with an unvalidated value.\n\nGUARD AT THE CALL SITE\nUse the fallible factory in a guard expression to attempt construction and only call the function on success:\n  if validEmail <- EmailAddress().of(\"alice@example.com\")\n    msg <- registerUser(\"Alice\", validEmail)\n    stdout.println(msg)\n  else\n    stdout.println(\"Invalid email format\")\n\n  if invalidEmail <- EmailAddress().of(\"not-an-email\")\n    msg <- registerUser(\"Bob\", invalidEmail)\n    stdout.println(msg)\n  else\n    stdout.println(\"Invalid email format\")\n\nThe '.of(value)' factory returns an unset EmailAddress when validation fails (a bare EmailAddress(value) would PANIC on a set invalid value instead). The block under 'if' only runs when construction succeeded. The 'else' handles the rejected case explicitly.\n\nKEY ADVANTAGES\n1. Validation lives in the TYPE, not scattered through call sites.\n2. Functions taking EmailAddress are guaranteed to receive valid values.\n3. No exceptions for predictable input failures — unset is the natural representation.\n4. The compiler enforces that you check 'isSet' before use.\n\nSee Q269 for input validation patterns. See Q257 for constrained type basics. See Q74 for guard expressions.","ek9Example":"defines module qa.security.emailtype\n\n  defines type\n\n    EmailAddress as String constrain as\n      matches /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$/\n\n  defines function\n\n    registerUser() as pure\n      ->\n        userName as String\n        email as EmailAddress\n      <- rtn as String: `Registered ${userName} with ${email}`\n\n  defines program\n\n    EmailValidationDemo()\n      stdout <- Stdout()\n\n      if validEmail <- EmailAddress().of(\"alice@example.com\")\n        msg <- registerUser(\"Alice\", validEmail)\n        stdout.println(msg)\n      else\n        stdout.println(\"Invalid email format\")\n\n      if invalidEmail <- EmailAddress().of(\"not-an-email\")\n        msg <- registerUser(\"Bob\", invalidEmail)\n        stdout.println(msg)\n      else\n        stdout.println(\"Invalid email format\")","migrationContext":"Java: validate-on-setter or Bean Validation @Email annotation. Kotlin: Result<Email> or sealed Either type. Rust: newtype struct Email(String) with try_from validation. Scala: Refined types or smart constructors. Python: pydantic EmailStr or manual regex check. EK9: 'defines type X as String constrain as matches /regex/' then X().of(value) produces unset on validation failure, composing with guard expressions for type-safe validation at the boundary.","keywords":["constrained type","domain type","email","guard","matches","newtype","regex","validation"],"primaryTopics":["constrained type with regex","email validation","guard with constructor"],"typicalErrors":[{"error":"E50010","correct":"EmailAddress as String constrain as\n      matches /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$/","incorrect":"EmailAddress as UserRecord constrain as\n      matches /^.+@.+$/","explanation":"Constrained types can only be built from primitive types like String, Integer or Float. User-defined records and classes cannot be constrained because they lack the comparison and pattern-matching semantics required. See ek9 -h E50010 for details."}],"companions":[]}
{"id":1242,"category":"Generics","question":"Create me a generic Validator class of type T with two abstract methods (check and describe), and a dynamic class implementation that validates a Date by checking it falls within a known year range.","url":"https://ek9.io/qa/QA1242.html","alternatePhrasings":["Write a generic abstract Validator with two abstract methods, instantiated for Date.","Implement a generic Validator that has both a check and a describe method.","Show me how to extend a multi-method generic abstract for the Date type.","Build a parameterised Validator with check and describe overrides for Date values."],"answer":"When a generic abstract class declares more than one abstract method, the dynamic class extending it must override EVERY abstract method. The compiler refuses partial implementations.\n\nGENERIC ABSTRACT WITH MULTIPLE ABSTRACTS\n  Validator of type T as abstract\n    check() as abstract\n      -> arg0 as T\n      <- rtn as Boolean?\n    describe() as abstract\n      -> arg0 as T\n      <- rtn as String?\n    override operator ? as pure\n      <- rtn as Boolean: true\n\nDYNAMIC CLASS WITH ALL OVERRIDES\nEvery abstract must be supplied an override. Here we instantiate the generic for the Date type and check whether a date falls between two known years:\n  appointmentValidator <- () extends Validator of Date as class\n    override check()\n      -> arg0 as Date\n      <- rtn as Boolean: arg0 >= EARLIEST_VALID_DATE and arg0 <= LATEST_VALID_DATE\n    override describe()\n      -> arg0 as Date\n      <- rtn as String: \"Date \" + $arg0\n\nEach overridden method independently uses the type-substituted parameter (arg0 as Date). Methods can refer to date-specific operators like '>=' that the abstract knows nothing about.\n\nUSAGE\n  scheduledDate <- 2025-06-15\n  stdout.println($appointmentValidator.check(scheduledDate))\n  stdout.println(appointmentValidator.describe(scheduledDate))\n\nKEY POINT\nThe SAME generic class works for ANY type T where the override body can produce sensible behaviour. Use String for label processing, Date for temporal validation, Money for currency checking, Dict for lookup containers — the generic carries no assumptions beyond what its abstract methods require.\n\nSee Q1234 for single-method generic extension. See Q197 for extending generics. See Q1235 for capture combined with generic extension.","ek9Example":"defines module qa.genericsdeep.validatordatemultimethod\n\n  defines constant\n\n    EARLIEST_VALID_DATE <- 2024-01-01\n\n    LATEST_VALID_DATE <- 2030-12-31\n\n  defines class\n\n    Validator of type T as abstract\n\n      check() as abstract\n        -> arg0 as T\n        <- rtn as Boolean?\n\n      describe() as abstract\n        -> arg0 as T\n        <- rtn as String?\n\n      override operator ? as pure\n        <- rtn as Boolean: true\n\n  defines program\n\n    ValidatorDateDemo()\n      stdout <- Stdout()\n\n      appointmentValidator <- () extends Validator of Date as class\n        override check()\n          -> arg0 as Date\n          <- rtn as Boolean: arg0 >= EARLIEST_VALID_DATE and arg0 <= LATEST_VALID_DATE\n        override describe()\n          -> arg0 as Date\n          <- rtn as String: \"Date \" + $arg0\n\n      scheduledDate <- 2025-06-15\n      stdout.println($appointmentValidator.check(scheduledDate))\n      stdout.println(appointmentValidator.describe(scheduledDate))","migrationContext":"Java: anonymous inner class extending Validator<Date> { @Override check, @Override describe }. Kotlin: object : Validator<LocalDate>() { override fun check, override fun describe }. Scala: new Validator[LocalDate] { def check; def describe }. Rust: impl Validator<NaiveDate> for ValidatorImpl { fn check; fn describe }. EK9: dynamic class form with all abstract methods overridden in one expression, parameterised for any type.","keywords":["Date","Validator","abstract","dynamic class","generic","multiple methods","of type T","override"],"primaryTopics":["multi-method generic","Date type parameter","abstract method completeness"],"typicalErrors":[{"error":"E07140","correct":"override check()\n          -> arg0 as Date\n          <- rtn as Boolean: arg0 >= EARLIEST_VALID_DATE and arg0 <= LATEST_VALID_DATE\n        override describe()\n          -> arg0 as Date\n          <- rtn as String: \"Date \" + $arg0","incorrect":"override check()\n          -> arg0 as Date\n          <- rtn as Boolean: arg0 >= EARLIEST_VALID_DATE and arg0 <= LATEST_VALID_DATE","explanation":"Both abstract methods (check and describe) must be overridden in the dynamic class. Leaving describe unimplemented means the method cannot be resolved at the call site. See ek9 -h E07140 for details."}],"companions":[]}
{"id":1243,"category":"Generics","question":"Write me a generic Transformer class with two type parameters K and V, and a dynamic class implementation that maps String keys to Integer values via a transform method.","url":"https://ek9.io/qa/QA1243.html","alternatePhrasings":["Create a generic abstract class with two type parameters K and V.","Show me how to declare a generic with multiple type parameters in EK9.","Implement a Transformer of (K, V) and use it for String to Integer mapping.","Build a parameterised type with key and value type parameters."],"answer":"Generic classes can declare multiple type parameters using the syntax 'of type (K, V)' with parameters in parentheses. Both parameters can appear in the abstract method signatures and are independently substituted at the extension site.\n\nGENERIC WITH TWO TYPE PARAMETERS\n  Transformer of type (K, V) as abstract\n    transform() as abstract\n      -> key as K\n      <- rtn as V?\n    override operator ? as pure\n      <- rtn as Boolean: true\n\nThe parameter names K and V are conventional but you can use any identifier. The 'of type (K, V)' form is required when there is more than one type parameter.\n\nDYNAMIC CLASS WITH BOTH SUBSTITUTIONS\nWhen extending, supply both concrete types in the same parenthesised form:\n  transformer <- () extends Transformer of (String, Integer) as class\n    override transform()\n      -> key as String\n      <- rtn as Integer: length of key\n\nBoth parameters are substituted simultaneously: K becomes String and V becomes Integer. The override signature uses the concrete types throughout.\n\nUSAGE\n  result <- transformer.transform(\"EK9\")\n  stdout.println($result)\n\nKEY POINTS\n1. Use 'of type (K, V)' with parentheses for multi-parameter generics.\n2. Use 'of (Type1, Type2)' at the extension site to supply both.\n3. Each type parameter can appear in any position in any method signature.\n\nSee Q196 for multi-parameter generics. See Q1234 for single-parameter generic basics. See Q1242 for multi-method generics.","ek9Example":"defines module qa.genericsdeep.transformerkv\n\n  defines class\n\n    Transformer of type (K, V) as abstract\n\n      transform() as abstract\n        -> key as K\n        <- rtn as V?\n\n      override operator ? as pure\n        <- rtn as Boolean: true\n\n  defines program\n\n    TransformerKVDemo()\n      stdout <- Stdout()\n\n      transformer <- () extends Transformer of (String, Integer) as class\n        override transform()\n          -> key as String\n          <- rtn as Integer: length of key\n\n      result <- transformer.transform(\"EK9\")\n      stdout.println($result)","migrationContext":"Java: class Transformer<K, V> { abstract V transform(K key); }. Kotlin: abstract class Transformer<K, V> { abstract fun transform(key: K): V }. Scala: trait Transformer[K, V] { def transform(key: K): V }. Rust: trait Transformer<K, V> { fn transform(&self, key: K) -> V }. EK9: 'of type (K, V)' parenthesised form for multi-parameter generics.","keywords":["K","Transformer","V","generic","key value","multi parameter","of type","two type parameters"],"primaryTopics":["multi-parameter generic","of type (K, V)","two type substitutions"],"typicalErrors":[{"error":"E50001","correct":"Transformer of type (K, V) as abstract","incorrect":"Transformer of type K, V as abstract","explanation":"When declaring multiple type parameters, parentheses are required: 'of type (K, V)'. Without parentheses the parser cannot tell where the parameter list ends. See ek9 -h E50001 for details."}],"companions":[]}
{"id":1244,"category":"Dispatcher Validation","question":"Create me a dispatcher for a deep class hierarchy (Shape -> Polygon -> Quadrilateral -> Square) where the Polygon handler catches Quadrilateral but a more specific Square handler exists.","url":"https://ek9.io/qa/QA1244.html","alternatePhrasings":["Show me how cost-based dispatch picks the closest handler in a deep inheritance tree.","Write a dispatcher that demonstrates exact-match wins over superclass-match.","Build a renderer that dispatches across a four-level hierarchy.","Implement a dispatcher with handlers for some intermediate types but not all."],"answer":"Dispatchers select the handler whose parameter type most closely matches the runtime type of the argument, walking up the inheritance chain until a handler is found. This is COST-BASED dispatch — exact match has cost 0, parent has cost 1, grandparent cost 2, and so on.\n\nDEEP INHERITANCE\n  Shape as abstract\n    default operator ?\n  Polygon is Shape as open\n    default operator ?\n  Quadrilateral is Polygon as open\n    default operator ?\n  Square is Quadrilateral\n    default operator ?\n\nNote 'as open' on intermediate classes — without it, EK9 closes the class and you cannot extend it further. Square is closed (default) because it is a leaf.\n\nDISPATCHER WITH PARTIAL HANDLERS\nThree handlers cover four types in the hierarchy:\n  GeometryRenderer\n    render() as dispatcher\n      -> shape as Shape\n      <- rtn as String: \"Shape handler\"\n    render()\n      -> shape as Polygon\n      <- rtn as String: \"Polygon handler\"\n    render()\n      -> shape as Square\n      <- rtn as String: \"Square handler\"\n\nDISPATCH BEHAVIOUR\n  renderer.render(Polygon())        // \"Polygon handler\" (exact match)\n  renderer.render(Quadrilateral())  // \"Polygon handler\" (Quadrilateral has no handler, walks up to Polygon)\n  renderer.render(Square())         // \"Square handler\" (exact match wins over Polygon)\n\nKEY INSIGHT\nFor Quadrilateral, the dispatcher walks up the hierarchy: no Quadrilateral handler -> try Polygon (found, use it). For Square, the exact handler exists so it wins even though Polygon is also a valid match (lower cost wins).\n\nThis avoids the visitor pattern boilerplate found in Java/C#: you only need handlers for the types you actually want to differentiate.\n\nSee Q620 for hierarchy dispatch rules. See Q616 for ambiguity detection. See Q1236 for sealed exhaustive dispatch.","ek9Example":"defines module qa.dispatcher.deephierarchy\n\n  defines class\n\n    Shape as abstract\n      default operator ?\n\n    Polygon is Shape as open\n      default operator ?\n\n    Quadrilateral is Polygon as open\n      default operator ?\n\n    Square is Quadrilateral\n      default operator ?\n\n    GeometryRenderer\n\n      render() as dispatcher\n        -> shape as Shape\n        <- rtn as String: \"Shape handler\"\n\n      render()\n        -> shape as Polygon\n        <- rtn as String: \"Polygon handler\"\n\n      render()\n        -> shape as Square\n        <- rtn as String: \"Square handler\"\n\n  defines program\n\n    DeepHierarchyDispatchDemo()\n      stdout <- Stdout()\n      renderer <- GeometryRenderer()\n\n      stdout.println(renderer.render(Polygon()))\n      stdout.println(renderer.render(Quadrilateral()))\n      stdout.println(renderer.render(Square()))","migrationContext":"Java: visitor pattern with accept(visitor) on each subclass — must implement EVERY visit method. Scala: pattern matching on case classes — order matters, exhaustiveness optional. Kotlin: when expression with class checks — must have 'else' or sealed classes. Rust: match on enum variants — exhaustive by default. EK9: dispatcher walks the hierarchy by cost, with explicit fallback in the entry method's body.","keywords":["cost based","deep hierarchy","dispatcher","exact match","fallback","inheritance","open","walk up"],"primaryTopics":["cost-based dispatch","deep hierarchy dispatcher","open class hierarchy"],"typicalErrors":[{"error":"E05030","correct":"Polygon is Shape as open","incorrect":"Polygon is Shape","explanation":"EK9 classes are CLOSED by default. To allow further extension (Quadrilateral extending Polygon), declare the parent 'as open'. Leaf classes can stay closed. See ek9 -h E05030 for details."}],"companions":[]}
{"id":1245,"category":"Dispatcher Validation","question":"Create me a two-parameter dispatcher Combiner with handlers for (Circle, Circle) and (Circle, Rectangle), demonstrating that parameter order matters and unmatched combinations fall back to the entry method.","url":"https://ek9.io/qa/QA1245.html","alternatePhrasings":["Show me a dispatcher that resolves on TWO parameter types simultaneously.","Implement a combine method that dispatches differently for different shape pairs.","Build a multi-method-style dispatcher with two parameters in EK9.","Write a dispatcher demonstrating asymmetric handler coverage on parameter pairs."],"answer":"EK9 dispatchers can resolve on multiple parameter types at once. With two parameters, the dispatcher matches on the runtime type of EACH parameter independently, so handler selection considers the full combination, not just the first parameter.\n\nSHAPE HIERARCHY\n  Shape as abstract\n    default operator ?\n  Circle is Shape\n    default operator ?\n  Rectangle is Shape\n    default operator ?\n\nTWO-PARAMETER DISPATCHER\n  Combiner\n    combine() as dispatcher\n      ->\n        s1 as Shape\n        s2 as Shape\n      <- rtn as String: \"Generic\"\n    combine()\n      ->\n        s1 as Circle\n        s2 as Circle\n      <- rtn as String: \"Circle-Circle\"\n    combine()\n      ->\n        s1 as Circle\n        s2 as Rectangle\n      <- rtn as String: \"Circle-Rectangle\"\n\nThe entry method takes both parameters as the parent type Shape. Each handler narrows BOTH parameters to specific subtypes.\n\nDISPATCH BEHAVIOUR\n  combiner.combine(circle, circle)        // \"Circle-Circle\" — exact match\n  combiner.combine(circle, rectangle)     // \"Circle-Rectangle\" — exact match\n  combiner.combine(rectangle, circle)     // \"Generic\" — no (Rectangle, Circle) handler\n  combiner.combine(rectangle, rectangle)  // \"Generic\" — no handler for this pair\n\nKEY INSIGHT\nParameter ORDER matters: (Circle, Rectangle) and (Rectangle, Circle) are DIFFERENT dispatch keys. If you want them to behave the same, you must declare both handlers. Unhandled combinations fall through to the entry method's body, which acts as the default.\n\nThis is true multiple dispatch — found in CLOS, Julia, Common Lisp — but rare in mainstream languages. Java/C# require visitor or instanceof chains; EK9 makes it a one-liner per case.\n\nSee Q619 for two-parameter dispatch basics. See Q620 for hierarchy rules. See Q1244 for deep hierarchy single-parameter dispatch.","ek9Example":"defines module qa.dispatcher.twoparam\n\n  defines class\n\n    Shape as abstract\n      default operator ?\n\n    Circle is Shape\n      default operator ?\n\n    Rectangle is Shape\n      default operator ?\n\n    Combiner\n\n      combine() as dispatcher\n        ->\n          s1 as Shape\n          s2 as Shape\n        <- rtn as String: \"Generic\"\n\n      combine()\n        ->\n          s1 as Circle\n          s2 as Circle\n        <- rtn as String: \"Circle-Circle\"\n\n      combine()\n        ->\n          s1 as Circle\n          s2 as Rectangle\n        <- rtn as String: \"Circle-Rectangle\"\n\n  defines program\n\n    TwoParamDispatchDemo()\n      stdout <- Stdout()\n      combiner <- Combiner()\n\n      circle <- Circle()\n      rectangle <- Rectangle()\n\n      stdout.println(combiner.combine(circle, circle))\n      stdout.println(combiner.combine(circle, rectangle))\n      stdout.println(combiner.combine(rectangle, circle))\n      stdout.println(combiner.combine(rectangle, rectangle))","migrationContext":"Java: nested instanceof or double-dispatch via visitor. Scala: pattern match on tuple (s1, s2) match { case (Circle, Circle) => ... }. Common Lisp / CLOS: defmethod with two specialised parameters — the inspiration. Julia: multimethod with type ascriptions on each parameter. EK9: native two-parameter dispatcher with cost-based selection on both arguments.","keywords":["asymmetric","combine","dispatcher","fallback","multiple dispatch","parameter order","two parameter"],"primaryTopics":["two-parameter dispatch","multiple dispatch","asymmetric handler coverage"],"typicalErrors":[{"error":"E01082","correct":"combine() as dispatcher\n        ->\n          s1 as Shape\n          s2 as Shape","incorrect":"combine() as dispatcher\n        -> s1 as Shape\n        -> s2 as Shape","explanation":"Multiple parameters are declared with a single '->' followed by an indented parameter block. Repeating '->' for each parameter is a parser-level syntax error. See ek9 -h E01082 for details."}],"companions":[]}
{"id":1246,"category":"Security and Sanitization","question":"Create me an AuditLogger class whose constructor takes a sanitized String log message, where construction with malicious input (file paths, terminal escape sequences, traceback fragments) leaves the logger in a 'rejected' state.","url":"https://ek9.io/qa/QA1246.html","alternatePhrasings":["Show me how the sanitized keyword works on a constructor parameter for an audit logger.","Write a logging class that rejects malicious log messages at construction time.","Implement an AuditLogger that validates its incoming message via the sanitized modifier.","Build an object that records whether its construction message was clean or rejected."],"answer":"The 'sanitized' modifier works on constructor parameters identically to function and method parameters. When a class is constructed with malicious input, the constructor sees an UNSET parameter, allowing the object to set itself into a safe default state rather than throwing an exception.\n\nCLASS WITH SANITIZED CONSTRUCTOR PARAMETER\n  AuditLogger\n    message as String: \"<no message>\"\n    state as String: \"empty\"\n\n    AuditLogger()\n      -> incomingMessage as sanitized String\n\n      if incomingMessage?\n        message: incomingMessage\n        state: \"clean\"\n      else\n        state: \"rejected\"\n\n    getMessage()\n      <- rtn as String: message\n\n    getState()\n      <- rtn as String: state\n\n    default operator ?\n\nThe field defaults ensure the object is always well-formed. The constructor body branches on 'incomingMessage?' to record whether the input was accepted.\n\nDEMONSTRATING ACCEPTED AND REJECTED INPUTS\nThis example varies the malicious payload types — file path traversal, ANSI escape codes, traceback fragments, log injection — rather than the usual SQL/XSS strings, to teach the model that 'sanitized' applies to ANY untrusted input, not just web payloads:\n  // Clean log message\n  log1 <- AuditLogger(\"User alice logged in successfully\")\n  // \"clean\"\n\n  // Path traversal attempt\n  log2 <- AuditLogger(\"file=../../../etc/shadow\")\n  // \"rejected\"\n\n  // ANSI escape attempt\n  log3 <- AuditLogger(\"Login\\u001b[31m FAILED \\u001b[0m\")\n  // \"rejected\"\n\n  // Log injection (newline forging)\n  log4 <- AuditLogger(\"action=lookup\n2025-01-01 ADMIN_OVERRIDE\")\n  // \"rejected\"\n\nKEY ADVANTAGE\nThe object remains usable even when the input was malicious — it just records 'rejected' instead of 'clean'. The caller can inspect getState() to know the outcome. No exceptions, no try/catch, no nullable result wrapping.\n\nSee Q1237 for sanitized function parameters. See Q1247 for sanitized method parameters. See Q272 for defense in depth.","ek9Example":"defines module qa.security.auditloggerconstructor\n\n  defines class\n\n    AuditLogger\n      message as String: \"<no message>\"\n      state as String: \"empty\"\n\n      AuditLogger()\n        -> incomingMessage as sanitized String\n\n        if incomingMessage?\n          message: incomingMessage\n          state: \"clean\"\n        else\n          state: \"rejected\"\n\n      getMessage()\n        <- rtn as String: message\n\n      getState()\n        <- rtn as String: state\n\n      default operator ?\n\n  defines program\n\n    SanitizedAuditLoggerDemo()\n      stdout <- Stdout()\n\n      stdout.println(\"=== AuditLogger Sanitization Demo ===\")\n\n      cleanMessage <- \"User alice logged in successfully\"\n      log1 <- AuditLogger(cleanMessage)\n      stdout.println(\"Clean: \" + log1.getState())\n\n      pathTraversal <- \"file=../../../etc/shadow\"\n      log2 <- AuditLogger(pathTraversal)\n      stdout.println(\"Path: \" + log2.getState())\n\n      ansiEscape <- \"Login\\u001b[31m FAILED \\u001b[0m\"\n      log3 <- AuditLogger(ansiEscape)\n      stdout.println(\"ANSI: \" + log3.getState())\n\n      logInjection <- \"action=lookup\\n2025-01-01 ADMIN_OVERRIDE\"\n      log4 <- AuditLogger(logInjection)\n      stdout.println(\"Inject: \" + log4.getState())\n\n      stdout.println(\"=== Complete ===\")","migrationContext":"Java: validation in setters with throw on invalid input, or Optional<AuditLogger> wrapper. Kotlin: data class with init { require(...) } that throws. Rust: TryFrom<String> for AuditLogger with Result return type. Python: __init__ raises ValueError. EK9: 'sanitized' modifier on the constructor parameter — input is unset if rejected, object always constructs successfully with a state field indicating which path was taken.","keywords":["ANSI escape","AuditLogger","audit","constructor","input validation","log injection","path traversal","sanitized"],"primaryTopics":["sanitized constructor parameter","audit logger validation","log injection prevention"],"typicalErrors":[{"error":"E50001","correct":"-> incomingMessage as sanitized String","incorrect":"-> sanitized incomingMessage as String","explanation":"The 'sanitized' modifier comes BEFORE the type, AFTER 'as'. The correct form is 'incomingMessage as sanitized String'. See ek9 -h E50001 for details."}],"companions":[]}
{"id":1247,"category":"Security and Sanitization","question":"Write me a QueryBuilder class with an addClause method that takes a sanitized String fragment, returning 'accepted' for clean fragments and 'rejected' for malicious ones (URL injection, LDAP injection, NoSQL operator injection).","url":"https://ek9.io/qa/QA1247.html","alternatePhrasings":["Show me how the sanitized keyword works on a method parameter for a query builder.","Create a QueryBuilder class whose addClause method validates its input via the sanitized modifier.","Implement a method that protects query state from injection through call arguments.","Build a class with a sanitized-parameter method that classifies query fragments."],"answer":"The 'sanitized' modifier works on method parameters identically to constructors and functions. When a method receives malicious input, the parameter is UNSET, so the method can branch on 'fragment?' to handle the rejected case without exceptions.\n\nCLASS WITH SANITIZED METHOD PARAMETER\n  QueryBuilder\n    accepted as Integer: 0\n    rejected as Integer: 0\n\n    default QueryBuilder()\n\n    addClause()\n      -> fragment as sanitized String\n      <- result as String: \"unset\"\n\n      if fragment?\n        accepted: accepted + 1\n        result: \"accepted\"\n      else\n        rejected: rejected + 1\n        result: \"rejected\"\n\n    getStats()\n      <- rtn as String: `accepted=${accepted} rejected=${rejected}`\n\nThe class keeps running counters of accepted and rejected fragments, demonstrating that the object remains usable across many calls — clean fragments increment the accepted counter, malicious ones increment rejected, neither path throws.\n\nDEMONSTRATING DIVERSE INJECTION TYPES\nThis example deliberately uses injection categories OTHER than SQL/XSS, to teach the model that 'sanitized' protects against any untrusted text:\n  qb <- QueryBuilder()\n\n  // Clean equality clause\n  qb.addClause(\"name = 'alice'\")           // accepted\n\n  // URL injection (parameter pollution)\n  qb.addClause(\"redirect=http://evil/?p=1\") // rejected\n\n  // LDAP injection\n  qb.addClause(\"(|(uid=*)(uid=*))\")          // rejected\n\n  // NoSQL operator injection\n  qb.addClause(\"$where: function() { return true }\") // rejected\n\n  // Clean range clause\n  qb.addClause(\"age >= 18\")                  // accepted\n\nWHEN TO USE METHOD-LEVEL SANITIZATION\nUse method-level sanitization when an object is constructed from trusted sources but later receives untrusted input through method calls — query builders, log appenders, file writers that accept paths, HTTP request builders that accept headers.\n\nSee Q1246 for sanitized constructor parameters. See Q1237 for sanitized function parameters. See Q272 for defense in depth.","ek9Example":"defines module qa.security.querybuildermethod\n\n  defines class\n\n    QueryBuilder\n      accepted as Integer: 0\n      rejected as Integer: 0\n\n      default QueryBuilder()\n\n      addClause()\n        -> fragment as sanitized String\n        <- result as String: \"unset\"\n\n        if fragment?\n          accepted: accepted + 1\n          result: \"accepted\"\n        else\n          rejected: rejected + 1\n          result: \"rejected\"\n\n      getStats()\n        <- rtn as String: `accepted=${accepted} rejected=${rejected}`\n\n      default operator ?\n\n  defines program\n\n    SanitizedQueryBuilderDemo()\n      stdout <- Stdout()\n      qb <- QueryBuilder()\n\n      stdout.println(\"=== QueryBuilder Sanitization Demo ===\")\n\n      cleanEquality <- \"name = 'alice'\"\n      stdout.println(\"Equality: \" + qb.addClause(cleanEquality))\n\n      urlInjection <- \"redirect=http://evil/?p=1\"\n      stdout.println(\"URL: \" + qb.addClause(urlInjection))\n\n      ldapInjection <- \"(|(uid=*)(uid=*))\"\n      stdout.println(\"LDAP: \" + qb.addClause(ldapInjection))\n\n      nosqlInjection <- \"$where: function() { return true }\"\n      stdout.println(\"NoSQL: \" + qb.addClause(nosqlInjection))\n\n      cleanRange <- \"age >= 18\"\n      stdout.println(\"Range: \" + qb.addClause(cleanRange))\n\n      stdout.println(qb.getStats())\n      stdout.println(\"=== Complete ===\")","migrationContext":"Java: validation inside the method body, or runtime libraries like Apache Validator. Python: input validation with re.match before use. Rust: validate by attempting to construct a newtype from the input. C#: data annotations validators on parameters. EK9: 'sanitized' modifier on the parameter — input is unset on rejection, no exception, no manual validation code.","keywords":["LDAP injection","NoSQL injection","QueryBuilder","URL injection","method","method validation","object protection","sanitized"],"primaryTopics":["sanitized method parameter","query builder validation","diverse injection categories"],"typicalErrors":[{"error":"E50001","correct":"addClause()\n        -> fragment as sanitized String","incorrect":"addClause(fragment as sanitized String)","explanation":"EK9 method parameters use the '->' arrow syntax on a separate indented line, not parenthesised parameters. See ek9 -h E50001 for details."}],"companions":[]}
{"id":1248,"category":"Design Patterns and Idioms","question":"Implement me a chain-of-responsibility pattern using abstract function handlers, where each handler tries to process a String and either returns a result or returns unset to pass on to the next handler.","url":"https://ek9.io/qa/QA1248.html","alternatePhrasings":["Create a chain of handlers that each get a chance to process a value.","Show me chain-of-responsibility in EK9 using abstract functions.","Build a series of handlers that try in order until one succeeds.","Write a pattern where multiple processors are tried until one returns a value."],"answer":"Chain of responsibility in EK9 uses an abstract function as the handler interface and a list of dynamic functions as the chain. A driver function walks the chain, returning the first non-unset result. Pure and concise — no class hierarchy needed.\n\nHANDLER INTERFACE\n  Handler() as pure abstract\n    -> request as String\n    <- rtn as String?\n\nThe handler returns String? — set when handled, unset when passing on.\n\nDRIVER FUNCTION\nThe driver walks the chain and stops at the first set result, using guarded assignment so the first match wins:\n  processChain() as pure\n    ->\n      request as String\n      handlers as List of Handler\n    <- rtn as String: String()\n    for handler in handlers\n      attempt <- handler(request)\n      if attempt?\n        rtn :=? attempt\n\nThe :=? operator only assigns if rtn is currently unset, so subsequent handler hits are ignored.\n\nDYNAMIC FUNCTION HANDLERS\nEach handler is a dynamic function with the abstract signature:\n  emailHandler <- () is Handler as pure function\n    rtn: request contains \"@\" <- \"email: \" + request : String()\n\n  urlHandler <- () is Handler as pure function\n    rtn: request contains \"http\" <- \"url: \" + request : String()\n\n  fallbackHandler <- () is Handler as pure function\n    rtn: \"text: \" + request\n\nThe ternary 'condition <- ifTrue : ifFalse' returns one or the other inline.\n\nUSAGE\n  chain <- [emailHandler, urlHandler, fallbackHandler]\n  stdout.println(processChain(\"alice@example.com\", chain))\n  stdout.println(processChain(\"http://ek9.io\", chain))\n  stdout.println(processChain(\"hello world\", chain))\n\nKEY ADVANTAGE\nNo class hierarchy, no setNext() boilerplate, no null checks. The handlers are values that compose into a List, and the driver is one short pure function. Adding a new handler is one line — instantiate a dynamic function and put it in the list.\n\nSee Q1238 for strategy pattern (single handler). See Q214 for strategy pattern basics. See Q284 for find-first via guarded assignment.","ek9Example":"defines module qa.patterns.chainresponsibility\n\n  defines function\n\n    Handler() as pure abstract\n      -> request as String\n      <- rtn as String?\n\n    processChain() as pure\n      ->\n        request as String\n        handlers as List of Handler\n      <- rtn as String: String()\n      for handler in handlers\n        attempt <- handler(request)\n        if attempt?\n          rtn :=? attempt\n\n  defines program\n\n    ChainOfResponsibilityDemo()\n      stdout <- Stdout()\n\n      emailHandler <- () is Handler as pure function\n        if request contains \"@\"\n          rtn: \"email: \" + request\n        else\n          rtn: String()\n\n      urlHandler <- () is Handler as pure function\n        if request contains \"http\"\n          rtn: \"url: \" + request\n        else\n          rtn: String()\n\n      fallbackHandler <- () is Handler as pure function\n        rtn: \"text: \" + request\n\n      chain <- [emailHandler, urlHandler, fallbackHandler]\n\n      stdout.println(processChain(\"alice@example.com\", chain))\n      stdout.println(processChain(\"http://ek9.io\", chain))\n      stdout.println(processChain(\"hello world\", chain))","migrationContext":"Java: abstract Handler class with setNext(), recursive handle() — verbose. Python: list of callables iterated until one returns non-None. JavaScript: array of functions reduced over input. Kotlin: list of functions called sequentially. EK9: List of abstract function instances + a small pure driver, with :=? to capture the first hit.","keywords":["abstract function","chain of responsibility","delegation","dynamic function","first match","handler","pattern","pipeline"],"primaryTopics":["chain of responsibility","handler list","first-match driver"],"typicalErrors":[{"error":"E07110","correct":"Handler() as pure abstract","incorrect":"Handler() as pure","explanation":"An abstract function with no body must be declared 'as abstract' (or 'as pure abstract'). 'as pure' alone is for functions WITH a body. See ek9 -h E07110 for details."}],"companions":[]}
{"id":1249,"category":"Data Flow Safety","question":"Write me a pure function classifyTemperature that takes a Float celsius and returns a String label, declaring the return variable WITHOUT a default and proving the compiler that every branch of the if/else chain initialises it.","url":"https://ek9.io/qa/QA1249.html","alternatePhrasings":["Show me how to satisfy data flow analysis when there is no default value on the return variable.","Create a function whose every branch must explicitly assign the return variable.","Implement a temperature classifier that uses an explicit final else to satisfy initialisation analysis.","Write a function with no default return where every branch covers initialisation."],"answer":"EK9 has TWO valid patterns for satisfying the compiler's data flow analysis on a return variable: declare with a default value (Pattern 1), or declare without a default and assign in EVERY branch including a final else (Pattern 2). This file demonstrates Pattern 2.\n\nPATTERN 2: NO DEFAULT, MANDATORY ELSE\n  classifyTemperature() as pure\n    -> celsius as Float\n    <- label as String?\n    if celsius >= HOT_THRESHOLD\n      label: \"hot\"\n    else if celsius >= WARM_THRESHOLD\n      label: \"warm\"\n    else if celsius >= COOL_THRESHOLD\n      label: \"cool\"\n    else\n      label: \"cold\"\n\nThe declaration 'label as String?' creates an UNSET return variable with no initial value. The compiler then requires that every reachable code path assigns 'label' before the function returns. Without the final 'else', a celsius below COOL_THRESHOLD would leave label uninitialised — and the compiler raises E08020.\n\nWHY USE PATTERN 2 INSTEAD OF PATTERN 1\nPattern 2 is preferable when:\n1. There is NO sensible default value for the return type.\n2. You want the compiler to FORCE you to think about every case.\n3. Adding a new branch should be a deliberate decision (no silent fallthrough).\n\nPattern 1 (default value, see Q1240) is preferable when:\n1. There IS a sensible default that should apply to any unhandled case.\n2. New branches add refinement to a baseline behaviour.\n\nUSAGE\n  stdout.println(`30C: ${classifyTemperature(30.0)}`)   // hot\n  stdout.println(`22C: ${classifyTemperature(22.0)}`)   // warm\n  stdout.println(`15C: ${classifyTemperature(15.0)}`)   // cool\n  stdout.println(`-5C: ${classifyTemperature(-5.0)}`)   // cold\n\nKEY POINT\nMagic literals (HOT_THRESHOLD, WARM_THRESHOLD, COOL_THRESHOLD) are extracted into a 'defines constant' block to satisfy E11064. Comparing against bare literals is rejected by EK9's quality analysis.\n\nSee Q1240 for Pattern 1 (default value). See Q633 for branch initialisation rules. See Q632 for definition order.","ek9Example":"defines module qa.dataflow.temperatureclassify\n\n  defines constant\n\n    HOT_THRESHOLD <- 25.0\n\n    WARM_THRESHOLD <- 18.0\n\n    COOL_THRESHOLD <- 10.0\n\n  defines function\n\n    classifyTemperature() as pure\n      -> celsius as Float\n      <- label as String?\n      if celsius >= HOT_THRESHOLD\n        label: \"hot\"\n      else if celsius >= WARM_THRESHOLD\n        label: \"warm\"\n      else if celsius >= COOL_THRESHOLD\n        label: \"cool\"\n      else\n        label: \"cold\"\n\n  defines program\n\n    TemperatureClassifyDemo()\n      stdout <- Stdout()\n\n      stdout.println(`30C: ${classifyTemperature(30.0)}`)\n      stdout.println(`22C: ${classifyTemperature(22.0)}`)\n      stdout.println(`15C: ${classifyTemperature(15.0)}`)\n      stdout.println(`-5C: ${classifyTemperature(-5.0)}`)","migrationContext":"Java: definite assignment requires every branch to assign — but allows null. Rust: every match arm must assign or use Option. Go: zero-value initialisation always works (but may hide bugs). Python: NameError at runtime. EK9: compile-time enforcement — either Pattern 1 (default) or Pattern 2 (else mandatory), no third option.","keywords":["E08020","branch initialisation","data flow","exhaustive","explicit else","if else chain","no default","return variable"],"primaryTopics":["explicit branch coverage","no-default return variable","mandatory else"],"typicalErrors":[{"error":"E08020","correct":"      if celsius >= HOT_THRESHOLD\n        label: \"hot\"\n      else if celsius >= WARM_THRESHOLD\n        label: \"warm\"\n      else if celsius >= COOL_THRESHOLD\n        label: \"cool\"","incorrect":"if celsius >= HOT_THRESHOLD\n        label: \"hot\"\n      else if celsius >= WARM_THRESHOLD\n        label: \"warm\"","explanation":"Without a final else and without a default value at declaration, label is uninitialised when celsius is below WARM_THRESHOLD. Either provide a default at declaration (Pattern 1) or add the final else (Pattern 2). See ek9 -h E08020 for details."}],"companions":[]}
{"id":1250,"category":"Classes and OOP","question":"Show me how to make a Repository class extensible so that InMemoryRepository can inherit from it without triggering E05030.","url":"https://ek9.io/qa/QA1250.html","alternatePhrasings":["How do I mark a Repository class as extensible in EK9?","Create a Repository parent class that an InMemoryRepository can extend.","I got E05030 on Repository — how do I allow InMemoryRepository to extend it?","Make a Repository class open so other repositories can inherit from it."],"answer":"EK9 classes are CLOSED by default. To allow another class to extend them, add 'as open' to the parent class declaration. Without 'as open' the compiler raises E05030 'not open to be extended'.\n\nPARENT CLASS AS OPEN\n  Repository as open\n    name as String: \"<unnamed>\"\n    default Repository()\n\nCHILD CLASS EXTENDS\n  InMemoryRepository extends Repository\n    default InMemoryRepository()\n\nThe 'extends' keyword only compiles if the parent has 'as open' (or is abstract — see Q1251). Any other class declaration is closed and produces E05030 when something tries to inherit from it.\n\nWHEN TO USE 'AS OPEN'\nUse 'as open' when you have a CONCRETE base class with a working default behaviour, and you WANT subclasses to provide alternative behaviour while still being usable on their own.\n\nSee Q1043 for diagnosing E05030. See Q784 for closed-by-default rationale. See Q1251 for abstract classes as an alternative.","ek9Example":"defines module qa.classesandoop.openrepository\n\n  defines class\n\n    Repository as open\n      name as String: \"<unnamed>\"\n\n      default Repository()\n\n      describe() as pure\n        <- rtn as String: \"Repository: \" + name\n\n      default operator ?\n\n    InMemoryRepository extends Repository\n      itemCount as Integer: 0\n\n      default InMemoryRepository()\n\n      override describe() as pure\n        <- rtn as String: `InMemoryRepository with ${itemCount} items`\n\n      default operator ?\n\n  defines program\n\n    OpenRepositoryDemo()\n      stdout <- Stdout()\n\n      repo <- InMemoryRepository()\n      stdout.println(repo.describe())","migrationContext":"Java: classes open by default, use 'final' to close. Kotlin: closed by default, use 'open' keyword (same as EK9). Scala: classes open by default, use 'final' to close. C#: classes open by default, use 'sealed' to close. Swift: classes open by default, use 'final' to close. EK9: closed by default like Kotlin, 'as open' to allow extension.","keywords":["E05030","Repository","as open","closed","extends","extensible","inheritance"],"primaryTopics":["as open on class","making class extensible","E05030 fix"],"typicalErrors":[{"error":"E05030","correct":"Repository as open","incorrect":"Repository","explanation":"Without 'as open', Repository is closed and InMemoryRepository cannot extend it. Add 'as open' to the parent declaration. See ek9 -h E05030 for details."}],"companions":[],"oracleToolHint":{"tool":"ek9_scaffold","intent":"class","description":"Oracle can generate an open base class and extending subclass with correct 'as open' modifier."}}
{"id":1251,"category":"Classes and OOP","question":"Create me a Database abstract class that PostgresDatabase can extend, showing that 'as abstract' makes the class implicitly open to extension.","url":"https://ek9.io/qa/QA1251.html","alternatePhrasings":["How does 'as abstract' relate to E05030 and the open/closed rule?","Show me that an abstract class does not need 'as open' to be extensible.","Write a Database abstract parent and a PostgresDatabase concrete child.","Make a class extensible by marking it as abstract instead of as open."],"answer":"An ABSTRACT class in EK9 is IMPLICITLY open. You do NOT need 'as open' on an abstract class — the 'as abstract' modifier already allows extension because an abstract class is meaningless without a concrete subclass.\n\nABSTRACT PARENT\n  Database as abstract\n    connectionString as String: String()\n\n    default Database()\n\n    connect() as abstract\n      <- rtn as Boolean?\n\n    protocol() as pure\n      <- rtn as String: \"generic\"\n\nAn abstract class can have BOTH abstract methods (no body, subclass MUST implement) AND concrete methods (default behaviour subclass can override).\n\nCONCRETE CHILD\n  PostgresDatabase extends Database\n    default PostgresDatabase()\n\n    override connect() as pure\n      <- rtn as Boolean: true\n\n    override protocol() as pure\n      <- rtn as String: \"postgres\"\n\nThe child must override every abstract method. Concrete methods are optional to override.\n\nTWO WAYS TO MAKE A CLASS EXTENSIBLE\n- 'as open' — concrete class, has a working default, subclasses are optional\n- 'as abstract' — incomplete class, MUST be subclassed, implicitly open\n\nYou CANNOT write 'as abstract as open' or 'as open as abstract' — these are mutually exclusive syntactically.\n\nSee Q1250 for 'as open' on concrete classes. See Q1043 for diagnosing E05030. See Q784 for closed-by-default rationale.","ek9Example":"defines module qa.classesandoop.abstractdatabase\n\n  defines class\n\n    Database as abstract\n      connectionString as String: String()\n\n      default Database()\n\n      connect() as pure abstract\n        <- rtn as Boolean?\n\n      protocol() as pure\n        <- rtn as String: \"generic\"\n\n      default operator ?\n\n    PostgresDatabase extends Database\n      default PostgresDatabase()\n\n      override connect() as pure\n        <- rtn as Boolean: true\n\n      override protocol() as pure\n        <- rtn as String: \"postgres\"\n\n      default operator ?\n\n  defines program\n\n    AbstractDatabaseDemo()\n      stdout <- Stdout()\n\n      db <- PostgresDatabase()\n      stdout.println(`Protocol: ${db.protocol()}`)\n      stdout.println(`Connected: ${db.connect()}`)","migrationContext":"Java: abstract classes can be extended regardless of 'final'. Kotlin: abstract implies open. Scala: abstract implies extensible. C#: abstract classes are implicitly inheritable. Python: ABC classes work similarly. EK9: 'as abstract' makes the class implicitly open — no need for both modifiers.","keywords":["Database","abstract class","as abstract","extends","implicitly open","inheritance"],"primaryTopics":["as abstract implies open","abstract class extension","abstract vs open"],"typicalErrors":[{"error":"E07110","correct":"Database as abstract","incorrect":"Database as abstract as open","explanation":"You cannot combine 'as abstract' and 'as open'. An abstract class is implicitly open — 'as abstract' is sufficient on its own. See ek9 -h E07110 for details."}],"companions":[],"oracleToolHint":{"tool":"ek9_scaffold","intent":"class","description":"Oracle can generate an abstract class that is implicitly open for extension."}}
{"id":1252,"category":"Functions and Methods","question":"Write me an open logWriter function and a dynamic function instance that overrides its behaviour, demonstrating that 'as open' on a function allows dynamic instances to extend it.","url":"https://ek9.io/qa/QA1252.html","alternatePhrasings":["How do I make a function extensible in EK9 with 'as open'?","Show me a concrete function with 'as open' that a dynamic function can extend.","Create a logWriter function that allows a dynamic instance to override its body.","I got E05030 on my function — how do I allow a dynamic instance to extend it?"],"answer":"FUNCTIONS in EK9 follow the same closed-by-default rule as classes. To allow a dynamic function instance to extend a concrete function, mark the function 'as open'. Without 'as open' the compiler raises E05030 when something tries to extend it.\n\nOPEN CONCRETE FUNCTION\n  logWriter() as open\n    -> message as String\n    <- formatted as String: \"[default] \" + message\n\nThe function has a body (default behaviour) AND is marked 'as open' so dynamic instances can override it.\n\nDYNAMIC INSTANCE EXTENDING IT\n  consoleVariant <- () is logWriter as function\n    formatted: \"[console] \" + message\n\nThe '() is logWriter as function' creates a dynamic instance whose body replaces the default. The 'message' parameter and 'formatted' return variable come from the parent function's signature — they are visible inside the dynamic body.\n\nUSAGE\n  defaultLine <- logWriter(\"hello\")\n  consoleLine <- consoleVariant(\"hello\")\n  stdout.println(defaultLine)\n  stdout.println(consoleLine)\n  // Output:\n  // [default] hello\n  // [console] hello\n\nWHEN TO USE 'AS OPEN' ON A FUNCTION\nUse it when you have a default implementation that works fine for most callers, but you want callers to be able to provide a specialised dynamic instance for specific needs. If you want to FORCE a custom implementation (no default), use 'as abstract' instead (see Q1253).\n\nSee Q1250 for 'as open' on classes. See Q1253 for 'as abstract' functions. See Q1043 for diagnosing E05030.","ek9Example":"defines module qa.functionsandmethods.openlogwriter\n\n  defines function\n\n    logWriter() as open\n      -> message as String\n      <- formatted as String: \"[default] \" + message\n\n  defines program\n\n    OpenLogWriterDemo()\n      stdout <- Stdout()\n\n      consoleVariant <- () is logWriter as function\n        formatted: \"[console] \" + message\n\n      defaultLine <- logWriter(\"hello\")\n      consoleLine <- consoleVariant(\"hello\")\n      stdout.println(defaultLine)\n      stdout.println(consoleLine)","migrationContext":"Java: no standalone open functions, methods are open by default. Kotlin: top-level functions are not extensible. Python: functions can be reassigned but not 'extended'. EK9 is unusual in allowing function extension via 'as open' plus a dynamic instance — the parent function provides the default body, the dynamic instance overrides it.","keywords":["E05030","as open","dynamic instance","extend function","function","logWriter"],"primaryTopics":["open function","dynamic function extension","as open on function"],"typicalErrors":[{"error":"E05030","correct":"logWriter() as open\n      -> message as String\n      <- formatted as String: \"[default] \" + message","incorrect":"logWriter()\n      -> message as String\n      <- formatted as String: \"[default] \" + message","explanation":"Without 'as open', the function is closed and dynamic instances cannot extend it. Add 'as open' to the parent function. See ek9 -h E05030 for details."}],"companions":[],"oracleToolHint":{"tool":"ek9_scaffold","intent":"function","description":"Oracle can generate an open function that dynamic function instances can extend."}}
{"id":1253,"category":"Functions and Methods","question":"Create me an abstract transformer function that a dynamic function instance can extend — showing that 'as abstract' on a function makes it implicitly open.","url":"https://ek9.io/qa/QA1253.html","alternatePhrasings":["Does an abstract function need 'as open' in EK9?","Show me that an abstract function is implicitly extensible by dynamic instances.","Write an abstract transformer and a dynamic function that implements it.","How does 'as abstract' remove the need for 'as open' on a function?"],"answer":"An ABSTRACT function in EK9 is IMPLICITLY open — just like an abstract class. You do NOT need to write 'as open' alongside 'as abstract' because an abstract function has no body and is useless without a concrete dynamic instance.\n\nABSTRACT FUNCTION\n  transformer() as pure abstract\n    -> input as String\n    <- rtn as String?\n\nAn abstract function has a signature (parameters and return type) but NO body. It cannot be called directly — only through a dynamic instance that provides the body.\n\nDYNAMIC INSTANCE PROVIDING THE BODY\n  trimAndUppercase <- () is transformer as pure function\n    rtn: input.trim().upperCase()\n\nThe '() is transformer as pure function' creates a dynamic instance that supplies the missing body. The 'input' parameter and 'rtn' return variable come from the abstract function's signature.\n\nTWO WAYS TO MAKE A FUNCTION EXTENSIBLE\n- 'as open' — concrete function with default body, dynamic instances are optional (Q1252)\n- 'as abstract' — signature only, dynamic instances are REQUIRED\n\nAbstract functions are typically used as STRATEGY interfaces — different dynamic instances are chosen at runtime (see Q1238 for the strategy pattern).\n\nUSAGE\n  result <- trimAndUppercase(\"  hello ek9  \")\n  stdout.println(result)\n  // Output: HELLO EK9\n\nSee Q1252 for 'as open' functions. See Q1238 for strategy pattern using abstract functions. See Q1043 for E05030 diagnosis.","ek9Example":"defines module qa.functionsandmethods.abstracttransformer\n\n  defines function\n\n    transformer() as pure abstract\n      -> input as String\n      <- rtn as String?\n\n  defines program\n\n    AbstractTransformerDemo()\n      stdout <- Stdout()\n\n      trimAndUppercase <- () is transformer as pure function\n        rtn: input.trim().upperCase()\n\n      result <- trimAndUppercase(\"  hello ek9  \")\n      stdout.println(result)","migrationContext":"Java: abstract methods on interfaces, lambdas implement the interface. Kotlin: abstract fun + lambda. Scala: abstract def + anonymous function. Rust: trait method without default. Python: abstractmethod decorator. EK9: 'as abstract' on a standalone function, dynamic instances supply the body.","keywords":["abstract function","as abstract","dynamic instance","function","implicitly open","transformer"],"primaryTopics":["abstract function","as abstract implies open","dynamic instance body"],"typicalErrors":[{"error":"E07110","correct":"transformer() as pure abstract\n      -> input as String\n      <- rtn as String?","incorrect":"transformer() as pure open\n      -> input as String\n      <- rtn as String?","explanation":"A function with no body must be 'as abstract'. 'as open' is for functions WITH a default body that can be overridden. See ek9 -h E07110 for details."}],"companions":[],"oracleToolHint":{"tool":"ek9_scaffold","intent":"function","description":"Oracle can generate an abstract function that dynamic functions can implement."}}
{"id":1254,"category":"Debugging and Troubleshooting","question":"Walk me through fixing error E05030 'not open to be extended' for three different cases: extending a closed class, extending a closed function, and extending a built-in type.","url":"https://ek9.io/qa/QA1254.html","alternatePhrasings":["How do I fix E05030 in EK9?","Give me three worked examples of fixing E05030.","The compiler says 'not open to be extended' — show me the fixes.","Walk me through the three common causes of E05030 and how to resolve each."],"answer":"E05030 'not open to be extended' is raised whenever you try to extend a type that is closed. There are THREE distinct scenarios and each has a different fix.\n\nCASE 1: EXTENDING A CLOSED USER CLASS\nProblem: You own the parent class but forgot 'as open':\n  AuditLogger                     // CLOSED by default\n    default AuditLogger()\n\n  FileAuditLogger extends AuditLogger   // E05030\n    default FileAuditLogger()\n\nFix: add 'as open' to the parent:\n  AuditLogger as open               // Now extensible\n    default AuditLogger()\n\nCASE 2: EXTENDING A CLOSED USER FUNCTION\nProblem: You try to extend a concrete function that is closed:\n  formatter()                     // CLOSED by default\n    -> input as String\n    <- rtn as String: input.trim()\n\n  upper <- () is formatter as function   // E05030 — formatter is closed\n    rtn: input.trim().upperCase()\n\nFix: add 'as open' to the parent function:\n  formatter() as open               // Now extensible\n    -> input as String\n    <- rtn as String: input.trim()\n\nCASE 3: EXTENDING A BUILT-IN CLOSED TYPE\nProblem: Built-in types (List, Dict, Optional, Result, String, Integer) are ALWAYS closed and can never be extended:\n  StringList extends List of String   // E05030 — List is closed\n\nFix: use COMPOSITION instead — hold the built-in as a field:\n  StringCollection\n    items as List of String: List()\n\n    add()\n      -> item as String\n      items += item\n\n    size() as pure\n      <- rtn as Integer: length items\n\nCHOOSING THE RIGHT FIX\n- Case 1 and Case 2 are fine to fix with 'as open' IF you own the parent AND inheritance is the right design.\n- For Case 3 (built-in types) you MUST use composition. Built-ins are deliberately closed to prevent subclasses from breaking invariants.\n- Whenever possible, prefer composition over inheritance even for your own classes.\n\nSee Q1043 for the E05030 diagnosis flow. See Q1250 for 'as open' on classes. See Q1252 for 'as open' on functions.","ek9Example":"defines module qa.debugging.fixe05030cases\n\n  defines class\n\n    AuditLogger as open\n      default AuditLogger()\n\n      log()\n        -> message as String\n        stdout <- Stdout()\n        stdout.println(\"[AUDIT] \" + message)\n\n      default operator ?\n\n    FileAuditLogger extends AuditLogger\n      default FileAuditLogger()\n\n      override log()\n        -> message as String\n        stdout <- Stdout()\n        stdout.println(\"[FILE-AUDIT] \" + message)\n\n      default operator ?\n\n    StringCollection\n      items as List of String: List() of String\n\n      default StringCollection()\n\n      add()\n        -> item as String\n        items += item\n\n      size() as pure\n        <- rtn as Integer: length items\n\n      default operator ?\n\n  defines function\n\n    formatter() as open\n      -> input as String\n      <- rtn as String: input.trim()\n\n  defines program\n\n    FixE05030Demo()\n      stdout <- Stdout()\n\n      logger <- FileAuditLogger()\n      logger.log(\"case 1: class extension via 'as open'\")\n\n      upper <- () is formatter as function\n        rtn: input.trim().upperCase()\n      stdout.println(`case 2: open function extended dynamically -> ${upper(\"  ek9  \")}`)\n\n      collection <- StringCollection()\n      collection.add(\"case 3: composition\")\n      collection.add(\"holds a List\")\n      stdout.println(`case 3: collection size -> ${collection.size()}`)","migrationContext":"Java: final keyword closes classes, subclasses can bypass accidentally. Kotlin: closed by default like EK9, use 'open' keyword. Scala: final keyword closes. C#: sealed keyword closes. EK9: E05030 catches all three cases at compile time — no runtime surprise.","keywords":["E05030","built-in","closed","composition","extends","fix","open","three cases"],"primaryTopics":["E05030 fix scenarios","open vs composition","built-in closed types"],"typicalErrors":[{"error":"E07090","correct":"StringCollection\n      items as List of String: List()","incorrect":"StringList extends List of String","explanation":"Built-in types like List are always closed. Attempting to extend them causes a type resolution error because the compiler cannot resolve the extension. Use composition instead. See ek9 -h E07090 for details."}],"companions":[]}
{"id":1255,"category":"Operators and Expressions","question":"Create me a Product record and use the SINGLE-dollar $ operator to convert it to a plain-text human-readable String.","url":"https://ek9.io/qa/QA1255.html","alternatePhrasings":["Show me how to use the $ operator on a custom record for human-readable output.","Write a Product record with a $ operator that returns a plain String.","Give me a record example that uses just the $ operator, not $$.","How do I override operator $ to produce a String representation of a record?"],"answer":"The SINGLE-dollar operator $ produces a PLAIN-TEXT String representation of a value. It is the EK9 equivalent of Java's toString() or Python's __str__. Override 'operator $' on a user-defined record to control what $ returns.\n\nPRODUCT RECORD WITH $ OVERRIDE\n  Product\n    sku as String: String()\n    name as String: String()\n    price as Float: 0.0\n\n    Product()\n      ->\n        sku as String\n        name as String\n        price as Float\n      this.sku :=: sku\n      this.name :=: name\n      this.price :=: price\n\n    override operator $ as pure\n      <- rtn as String: `${sku} ${name} £${price}`\n\n    default operator ?\n\nUSAGE\n  product <- Product(\"SKU-42\", \"Widget\", 9.99)\n  plainText <- $product\n  stdout.println(plainText)\n  // Output: SKU-42 Widget £9.99\n\nWHAT $ DOES\n- Returns a String that is human-readable\n- Uses backtick string interpolation for flexible formatting\n- Is the OPPOSITE of $$ — $ is plain text, $$ is JSON (see Q1256)\n\nThis file uses ONLY the $ operator so the distinction from $$ is absolutely clear. Every use of the dollar sign here is single-dollar.\n\nSee Q1256 for the $$ JSON operator contrast. See Q1094 for $ and $$ in context. See Q887 for the JSON operator return type.","ek9Example":"defines module qa.operators.dollarstringproduct\n\n  defines record\n\n    Product\n      sku as String: String()\n      name as String: String()\n      price as Float: 0.0\n\n      Product()\n        ->\n          sku as String\n          name as String\n          price as Float\n        this.sku :=: sku\n        this.name :=: name\n        this.price :=: price\n\n      operator $ as pure\n        <- rtn as String: `${sku} ${name} £${price}`\n\n      default operator ?\n\n  defines program\n\n    DollarStringProductDemo()\n      stdout <- Stdout()\n\n      product <- Product(\"SKU-42\", \"Widget\", 9.99)\n      plainText <- $product\n      stdout.println(plainText)","migrationContext":"Java: toString() override. Kotlin: override fun toString(). Python: __str__. Rust: impl Display. Scala: override def toString. C#: override ToString(). EK9: 'operator $' produces a plain-text String. 'operator $$' produces JSON — the two are DIFFERENT.","keywords":["$","Product","dollar","human readable","plain text","string operator","toString"],"primaryTopics":["operator $","string conversion","plain text output"],"typicalErrors":[{"error":"E07580","correct":"operator $ as pure","incorrect":"operator $$ as pure","explanation":"'$' and '$$' are different operators: '$$' is the JSON operator and must return a JSON, while '$' returns plain text. See ek9 -h E07580 for details."}],"companions":[]}
{"id":1256,"category":"Operators and Expressions","question":"Create me a Customer record and use the DOUBLE-dollar $$ operator to convert it to a JSON representation — not plain text.","url":"https://ek9.io/qa/QA1256.html","alternatePhrasings":["Show me how to use the $$ operator on a custom record for JSON output.","Write a Customer record with a $$ operator that returns a JSON value.","Give me a record example that uses just the $$ operator, not $.","How do I override operator $$ to produce a JSON representation of a record?"],"answer":"The DOUBLE-dollar operator $$ produces a JSON representation of a value. It is distinct from the single-dollar $ operator (which produces plain text). Override 'operator $$' on a user-defined record to control what $$ returns — typically constructing a JSON object from the record's fields.\n\nCUSTOMER RECORD WITH $$ OVERRIDE\n  Customer\n    id as Integer: 0\n    firstName as String: String()\n    lastName as String: String()\n\n    Customer()\n      ->\n        id as Integer\n        firstName as String\n        lastName as String\n      this.id :=: id\n      this.firstName :=: firstName\n      this.lastName :=: lastName\n\n    override operator $$ as pure\n      <- rtn as JSON: JSON()\n        rtn.object()\n        rtn += JSON(\"id\", $id)\n        rtn += JSON(\"firstName\", firstName)\n        rtn += JSON(\"lastName\", lastName)\n\n    default operator ?\n\nUSAGE\n  customer <- Customer(1001, \"Ada\", \"Lovelace\")\n  jsonValue <- $$customer\n  stdout.println($jsonValue)\n  // Output: {\"id\":1001,\"firstName\":\"Ada\",\"lastName\":\"Lovelace\"}\n\nWHAT $$ DOES\n- Returns a JSON value (type JSON, not String)\n- Used to serialise records to wire format\n- Is the OPPOSITE of $ — $$ is JSON, $ is plain text (see Q1255)\n\nThis file uses ONLY the $$ operator so the distinction from $ is absolutely clear. Every use of a dollar sign operator here is double-dollar.\n\nNOTE ON THE DIFFERENCE\n- $ returns String (plain text, for humans)\n- $$ returns JSON (structured, for machines)\nThe two are DIFFERENT operators with DIFFERENT return types. Do not confuse them.\n\nSee Q1255 for the $ string operator contrast. See Q887 for the JSON operator return type. See Q1094 for $ and $$ in context.","ek9Example":"defines module qa.operators.dollardollarjsoncustomer\n\n  defines class\n\n    Customer\n      id as Integer: 0\n      firstName as String: String()\n      lastName as String: String()\n\n      Customer()\n        ->\n          id as Integer\n          firstName as String\n          lastName as String\n        this.id :=: id\n        this.firstName :=: firstName\n        this.lastName :=: lastName\n\n      operator $$ as pure\n        <- rtn as JSON: JSON()\n\n      override operator ? as pure\n        <- rtn as Boolean: id?\n\n  defines program\n\n    DollarDollarJsonCustomerDemo()\n      stdout <- Stdout()\n\n      customer <- Customer(1001, \"Ada\", \"Lovelace\")\n      jsonValue <- $$customer\n      stdout.println(`JSON: ${jsonValue}`)","migrationContext":"Java: custom toJson() methods or Jackson/Gson annotations. Kotlin: kotlinx.serialization @Serializable. Python: __json__ convention or dataclass asdict. Rust: serde Serialize. EK9: 'operator $$' is built-in syntax for JSON conversion, returning a JSON value directly.","keywords":["$$","Customer","JSON conversion","JSON operator","double dollar","serialization"],"primaryTopics":["operator $$","JSON conversion","$$ vs $"],"typicalErrors":[{"error":"E07570","correct":"operator $$ as pure","incorrect":"operator $ as pure","explanation":"Operator $ must return a String; to return JSON use the double-dollar $$ operator instead of $. See ek9 -h E07570 for details."}],"companions":[]}
{"id":1257,"category":"Operators and Expressions","question":"Write me a Booking class with a SINGLE-dollar $ operator that returns a human-readable reservation line — no JSON, just plain text.","url":"https://ek9.io/qa/QA1257.html","alternatePhrasings":["Show me another $ operator example on a custom class, not a record.","Give me a $-only Booking example — no $$ involved.","Override operator $ on a Booking class to produce a readable String line.","Create a worked example of $ (plain text only) on a reservation class."],"answer":"The SINGLE-dollar $ operator produces plain text. This file shows a Booking class where $ returns a formatted reservation line. NO $$ is used anywhere — this is a pure single-dollar example.\n\nBOOKING CLASS WITH $ OVERRIDE\n  Booking\n    reference as String: String()\n    guestName as String: String()\n    nights as Integer: 0\n\n    Booking()\n      ->\n        reference as String\n        guestName as String\n        nights as Integer\n      this.reference :=: reference\n      this.guestName :=: guestName\n      this.nights :=: nights\n\n    operator $ as pure\n      <- rtn as String: `Booking ${reference} for ${guestName} — ${nights} nights`\n\n    override operator ? as pure\n      <- rtn as Boolean: reference?\n\nUSAGE\n  booking <- Booking(\"BK-2024-0817\", \"Alice\", 3)\n  line <- $booking\n  stdout.println(line)\n  // Output: Booking BK-2024-0817 for Alice — 3 nights\n\nKEY POINT\nThe return type of $ is always String. The return type of $$ is JSON. They are DIFFERENT operators. This example uses ONLY $ — there is no $$ anywhere in the code.\n\nSee Q1255 for another $-only example (Product record). See Q1256 for a $$-only example (Customer). See Q887 for the operator contracts.","ek9Example":"defines module qa.operators.dollarstringbooking\n\n  defines class\n\n    Booking\n      reference as String: String()\n      guestName as String: String()\n      nights as Integer: 0\n\n      Booking()\n        ->\n          reference as String\n          guestName as String\n          nights as Integer\n        this.reference :=: reference\n        this.guestName :=: guestName\n        this.nights :=: nights\n\n      operator $ as pure\n        <- rtn as String: `Booking ${reference} for ${guestName} — ${nights} nights`\n\n      override operator ? as pure\n        <- rtn as Boolean: reference?\n\n  defines program\n\n    DollarStringBookingDemo()\n      stdout <- Stdout()\n\n      booking <- Booking(\"BK-2024-0817\", \"Alice\", 3)\n      line <- $booking\n      stdout.println(line)","migrationContext":"Java: toString() override on the class. Kotlin: override fun toString(). Python: __str__. Rust: impl Display for Booking. C#: override ToString(). EK9: 'operator $ as pure' returning String — plain text, never JSON.","keywords":["$","Booking","class","dollar","plain text","reservation","string operator"],"primaryTopics":["operator $ on class","plain text conversion","toString equivalent"],"typicalErrors":[{"error":"E07580","correct":"      operator $ as pure\n        <- rtn as String: `Booking ${reference} for ${guestName} — ${nights} nights`","incorrect":"operator $ as pure\n        <- rtn as JSON: JSON()","explanation":"$ must return String, not JSON. JSON is the return type for $$. See ek9 -h E07580 for details."}],"companions":[]}
{"id":1258,"category":"Operators and Expressions","question":"Create me an Invoice class with a DOUBLE-dollar $$ operator that returns a JSON value — no plain-text formatting, only JSON.","url":"https://ek9.io/qa/QA1258.html","alternatePhrasings":["Show me another $$ operator example on a custom class.","Give me a $$-only Invoice example — no single $.","Override operator $$ on an Invoice class to return JSON.","Create a worked example of $$ (JSON only) on a billing class."],"answer":"The DOUBLE-dollar $$ operator produces JSON. This file shows an Invoice class where $$ returns a JSON value. NO $ is used anywhere — this is a pure double-dollar example.\n\nINVOICE CLASS WITH $$ OVERRIDE\n  Invoice\n    invoiceNumber as String: String()\n    amount as Float: 0.0\n    paid as Boolean: false\n\n    Invoice()\n      ->\n        invoiceNumber as String\n        amount as Float\n        paid as Boolean\n      this.invoiceNumber :=: invoiceNumber\n      this.amount :=: amount\n      this.paid :=: paid\n\n    operator $$ as pure\n      <- rtn as JSON: JSON()\n\n    override operator ? as pure\n      <- rtn as Boolean: invoiceNumber?\n\nUSAGE\n  invoice <- Invoice(\"INV-2024-0042\", 125.50, false)\n  jsonValue <- $$invoice\n  stdout.println(`Invoice as JSON: ${jsonValue}`)\n\nKEY POINT\nThe return type of $$ is always JSON, never String. The return type of $ is always String, never JSON. They are DIFFERENT operators. This example uses ONLY $$ — there is no single $ operator here.\n\nCOMMON MISTAKE\nIt is tempting to think $$ is 'a stronger $' or 'escaped $'. It is NOT. It is a completely separate operator with a different purpose and a different return type.\n\nSee Q1256 for another $$-only example (Customer). See Q1255 for a $-only example (Product). See Q887 for E07580 on wrong JSON return type.","ek9Example":"defines module qa.operators.dollardollarjsoninvoice\n\n  defines class\n\n    Invoice\n      invoiceNumber as String: String()\n      amount as Float: 0.0\n      paid as Boolean: false\n\n      Invoice()\n        ->\n          invoiceNumber as String\n          amount as Float\n          paid as Boolean\n        this.invoiceNumber :=: invoiceNumber\n        this.amount :=: amount\n        this.paid :=: paid\n\n      operator $$ as pure\n        <- rtn as JSON: JSON()\n\n      override operator ? as pure\n        <- rtn as Boolean: invoiceNumber?\n\n  defines program\n\n    DollarDollarJsonInvoiceDemo()\n      stdout <- Stdout()\n\n      invoice <- Invoice(\"INV-2024-0042\", 125.50, false)\n      jsonValue <- $$invoice\n      stdout.println(`Invoice as JSON: ${jsonValue}`)","migrationContext":"Java: custom toJson() or Jackson ObjectMapper. Kotlin: kotlinx.serialization. Scala: circe or play-json. Rust: serde Serialize trait. Python: json.dumps or __json__. Go: json.Marshaler interface. EK9: built-in 'operator $$' with enforced JSON return type.","keywords":["$$","Invoice","JSON operator","billing","double dollar","serialization"],"primaryTopics":["operator $$ on class","JSON serialisation","billing example"],"typicalErrors":[{"error":"E07580","correct":"operator $$ as pure\n        <- rtn as JSON: JSON()","incorrect":"operator $$ as pure\n        <- rtn as String: `${invoiceNumber}`","explanation":"$$ must return JSON, not String. If you want a plain-text representation, use $ instead. See ek9 -h E07580 for details."}],"companions":[]}
{"id":1259,"category":"Operators and Expressions","question":"Show me how to use the ++ and -- operators on an Integer counter, demonstrating that they are statement-only and modify the variable in place.","url":"https://ek9.io/qa/QA1259.html","alternatePhrasings":["Increment and decrement an Integer using ++ and --.","Write a small program that uses ++ to count up and -- to count down.","Show me how Integer ++ works in EK9 — is it the same as Java?","Demonstrate that ++ on an Integer is statement-only and not an expression."],"answer":"EK9 supports ++ and -- on Integer (and many other types). They are STATEMENT-ONLY operators — they modify the variable in place and produce NO value. You cannot use them inside expressions.\n\nBASIC USAGE\n  visitorCount <- 0\n  visitorCount++       // OK: standalone statement\n  visitorCount++       // OK: each call increments by 1\n  visitorCount--       // OK: decrement by 1\n\nWHAT YOU CANNOT DO\n  copy <- visitorCount++   // ERROR: ++ produces no value to assign\n  if visitorCount++        // ERROR: ++ has no Boolean result\n  total <- a + b++         // ERROR: ++ cannot be inside an expression\n\nWHY STATEMENT-ONLY\nIn C/Java/JavaScript, ++ returns the OLD value (postfix) or NEW value (prefix). This creates subtle bugs:\n  x = counter++   // C: x gets old value\n  x = ++counter   // C: x gets new value\nEK9 eliminates this ambiguity by making ++ purely a side-effect statement. To get the value, use the variable separately AFTER the increment.\n\nCORRECT PATTERN\n  visitorCount <- 0\n  visitorCount++\n  current <- visitorCount       // Read the value separately\n  stdout.println(`After ++: ${current}`)\n\nUSAGE IN A LOOP\n  retries <- 0\n  maxRetries <- 3\n  while retries < maxRetries\n    retries++\n    stdout.println(`Attempt ${retries}`)\n\nThis QA uses Integer specifically. See Q1260 for Float ++/--, Q1261 for Date ++/--, and Q1262 for Enumeration ++/--. The same statement-only semantics apply to every type that supports ++ and --.\n\nSee Q241 for mutation operators overview. See Q238 for the complete operator set.","ek9Example":"defines module qa.operators.incrementinteger\n\n  defines program\n\n    IncrementIntegerDemo()\n      stdout <- Stdout()\n\n      visitorCount <- 0\n      visitorCount++\n      visitorCount++\n      visitorCount++\n      stdout.println(`After three ++: ${visitorCount}`)\n\n      visitorCount--\n      stdout.println(`After one --: ${visitorCount}`)\n\n      retries <- 0\n      maxRetries <- 3\n      while retries < maxRetries\n        retries++\n        stdout.println(`Attempt ${retries}`)","migrationContext":"C/C++: ++ and -- are expressions returning a value. Java: same as C, with prefix/postfix distinction. JavaScript: same. Python: no ++ or --, use += 1. Go: ++ is statement-only (no value), like EK9. Kotlin: inc()/dec() functions or += 1. Rust: no ++, use += 1. EK9: matches Go's design — statement-only, no value, no prefix/postfix ambiguity.","keywords":["++","--","Integer","counter","decrement","in place","increment","statement only"],"primaryTopics":["Integer ++ --","statement-only mutation","in-place increment"],"typicalErrors":[{"error":"E07510","correct":"      visitorCount++\n      visitorCount++","incorrect":"current <- visitorCount++","explanation":"++ is statement-only. It does not return a value, so it cannot be used as the right-hand side of an assignment. Increment first, then read the variable. See ek9 -h E07510 for details."}],"companions":[]}
{"id":1260,"category":"Operators and Expressions","question":"Show me how to use ++ and -- on a Float temperature reading, demonstrating that floating-point types also support in-place increment and decrement.","url":"https://ek9.io/qa/QA1260.html","alternatePhrasings":["Can I use ++ on a Float in EK9?","Show me Float ++ and -- in action.","Increment a Float reading in place.","Does Float support the same ++ -- operators as Integer in EK9?"],"answer":"Float supports the ++ and -- operators, just like Integer. Each ++ adds 1.0 to the value; each -- subtracts 1.0. They are STATEMENT-ONLY and modify the variable in place — exactly the same semantics as Integer ++/--.\n\nBASIC USAGE ON FLOAT\n  reading as Float: 23.5\n  reading++              // Now 24.5\n  reading++              // Now 25.5\n  reading--              // Now 24.5\n\nThe value changes by exactly 1.0 each time. There is no rounding, no precision loss beyond normal Float arithmetic.\n\nSAMPLING SCENARIO\nA temperature monitor that adjusts a setpoint by one degree at a time:\n  setpoint as Float: 20.0\n\n  // Three small adjustments up\n  setpoint++\n  setpoint++\n  setpoint++\n  // setpoint is now 23.0\n\n  // One adjustment down\n  setpoint--\n  // setpoint is now 22.0\n\n  stdout.println(`Final setpoint: ${setpoint}`)\n\nNO EXPRESSION USE\nLike Integer ++, Float ++ is statement-only. You cannot write 'next <- reading++' — that is a compile error. Increment first, then read.\n\nWHAT IF YOU WANT FRACTIONAL INCREMENT?\nUse the compound assignment += instead:\n  reading as Float: 23.5\n  reading += 0.1   // Now 23.6\nThe ++ operator always increments by 1.0. For other step sizes, use +=.\n\nSee Q1259 for Integer ++/--. See Q1261 for Date ++/--. See Q1262 for Enumeration ++/--. See Q241 for mutation operators overview.","ek9Example":"defines module qa.operators.incrementfloat\n\n  defines program\n\n    IncrementFloatDemo()\n      stdout <- Stdout()\n\n      setpoint as Float: 20.0\n      stdout.println(`Initial setpoint: ${setpoint}`)\n\n      setpoint++\n      setpoint++\n      setpoint++\n      stdout.println(`After three ++: ${setpoint}`)\n\n      setpoint--\n      stdout.println(`After one --: ${setpoint}`)\n\n      reading as Float: 23.5\n      reading++\n      stdout.println(`Reading after ++: ${reading}`)","migrationContext":"Java: float ++ supported, same statement semantics. C/C++: float ++ supported, increments by 1.0. Python: no ++ for any type. Rust: no ++ for any type, use += 1.0. Go: float ++ is statement-only. JavaScript: float ++ supported. EK9: matches the C-family conventions for Float — increment by 1.0, statement-only.","keywords":["++","--","Float","decrement","in place","increment","temperature"],"primaryTopics":["Float ++ --","floating-point increment","in-place mutation"],"typicalErrors":[{"error":"E07950","correct":"reading++","incorrect":"reading: reading++ + 1.0","explanation":"++ is statement-only. It cannot appear inside an expression. The compiler detects the invalid expression usage. To add 2.0, use 'reading += 2.0'. See ek9 -h E07950 for details."}],"companions":[]}
{"id":1261,"category":"Operators and Expressions","question":"Show me how to use ++ on a Date to advance one day at a time, iterating through a week starting from a known date.","url":"https://ek9.io/qa/QA1261.html","alternatePhrasings":["How does ++ work on a Date in EK9?","Increment a Date by one day using ++ in EK9.","Walk forward through dates one day at a time without using a Duration.","Show me Date ++ and -- in action."],"answer":"Date supports the ++ and -- operators. Each ++ advances the date by ONE DAY; each -- moves it back by ONE DAY. Like all ++/-- operators in EK9, these are STATEMENT-ONLY and modify the variable in place.\n\nADVANCING ONE DAY AT A TIME\n  startOfWeek <- 2025-03-10\n  current <- Date(startOfWeek)\n\n  current++   // 2025-03-11\n  current++   // 2025-03-12\n  current++   // 2025-03-13\n\nThe Date arithmetic understands month boundaries — incrementing 2025-03-31 by ++ produces 2025-04-01, not '2025-03-32'.\n\nITERATING A WEEK\n  startOfWeek <- 2025-03-10\n  current <- Date(startOfWeek)\n  daysToShow <- 7\n  daysShown <- 0\n\n  while daysShown < daysToShow\n    stdout.println(`Day ${daysShown + 1}: ${current}`)\n    current++\n    daysShown++\n\nThis prints seven consecutive dates without ever touching a Duration. The ++ operator IS the 'add one day' API for Date.\n\nGOING BACKWARDS\n  todayLike <- 2025-03-15\n  yesterday <- Date(todayLike)\n  yesterday--   // 2025-03-14\n\nLEAP YEAR AND MONTH BOUNDARIES\n  monthEnd <- 2024-02-28\n  next <- Date(monthEnd)\n  next++   // 2024-02-29 (2024 is a leap year)\n  next++   // 2024-03-01\n\nThe Date type handles leap years and month lengths automatically. You never need to compute the next valid date yourself.\n\nSee Q1259 for Integer ++/--. See Q1260 for Float ++/--. See Q1262 for Enumeration ++/--. See Q241 for mutation operators overview.","ek9Example":"defines module qa.operators.incrementdate\n\n  defines program\n\n    IncrementDateDemo()\n      stdout <- Stdout()\n\n      startOfWeek <- 2025-03-10\n      current <- Date(startOfWeek)\n      daysToShow <- 7\n      daysShown <- 0\n\n      while daysShown < daysToShow\n        stdout.println(`Day ${daysShown + 1}: ${current}`)\n        current++\n        daysShown++\n\n      yesterday <- 2025-03-15\n      previousDay <- Date(yesterday)\n      previousDay--\n      stdout.println(`Yesterday: ${previousDay}`)","migrationContext":"Java: LocalDate.plusDays(1) — method call, no operator. Kotlin: same as Java. Python: date + timedelta(days=1) — uses arithmetic with a delta. Rust: NaiveDate::succ_opt() — method returning Option. C#: DateTime.AddDays(1) — method call. JavaScript: setDate(getDate() + 1) — manual arithmetic. EK9: Date supports ++ directly — most concise way to step through days.","keywords":["++","--","Date","calendar","increment","iterate days","next day"],"primaryTopics":["Date ++ --","date arithmetic","advance by day"],"typicalErrors":[{"error":"E50060","correct":"current++","incorrect":"current.addDays(1)","explanation":"EK9 Date does not have an addDays method. The compiler cannot resolve the method call. Use the ++ operator to advance by one day, or += Duration() for larger steps. See ek9 -h E50060 for details."}],"companions":[]}
{"id":1262,"category":"Operators and Expressions","question":"Show me how to use ++ on an enumeration to walk through ProcessState values (Pending → Running → Complete), demonstrating that walking past the last value makes the variable unset.","url":"https://ek9.io/qa/QA1262.html","alternatePhrasings":["How does ++ work on an EK9 enumeration?","Walk through enumeration values using ++ in EK9.","Show me what happens when I increment past the last enum value.","Use the ++ and -- operators on a custom enum like ProcessState."],"answer":"EK9 enumerations support ++ (next value) and -- (previous value). Each ++ advances to the next declared value; -- moves to the previous one. When the variable would go past the FIRST or LAST declared value, it becomes UNSET — no exception, no wrap-around.\n\nDEFINE THE ENUMERATION\n  ProcessState\n    Pending\n    Running\n    Complete\n\nFour declared values in order. Pending is first; Complete is last.\n\nWALKING FORWARD\n  state as ProcessState: ProcessState.Pending\n  stdout.println(`start: ${state}`)\n\n  state++   // Running\n  stdout.println(`after ++ : ${state}`)\n\n  state++   // Complete\n  stdout.println(`after ++ : ${state}`)\n\n  state++   // unset — past the last value\n  stdout.println(`past last is set: ${state?}`)\n\nThe critical safety property: incrementing past the last value does NOT throw and does NOT wrap to the first. The variable becomes UNSET, which the ? operator detects.\n\nWALKING BACKWARDS\n  state2 as ProcessState: ProcessState.Complete\n  state2--   // Running\n  state2--   // Pending\n  state2--   // unset — before the first value\n  stdout.println(`before first is set: ${state2?}`)\n\nSAFE CYCLING PATTERN\n  current as ProcessState: ProcessState.Pending\n  while current?\n    stdout.println(`processing: ${current}`)\n    current++\n\nThe loop terminates automatically when current becomes unset after the last value. No manual bounds tracking, no off-by-one errors.\n\nWHY THIS MATTERS\nMost languages either throw on out-of-bounds, wrap to the first value, or require manual bounds checking. EK9's unset-on-boundary semantics compose naturally with the ? operator and guard expressions.\n\nSee Q1259 for Integer ++/--. See Q1260 for Float ++/--. See Q1261 for Date ++/--. See Q220 for full enumeration navigation.","ek9Example":"defines module qa.operators.incrementenum\n\n  defines type\n\n    ProcessState\n      Pending\n      Running\n      Complete\n\n  defines program\n\n    IncrementEnumDemo()\n      stdout <- Stdout()\n\n      state as ProcessState: ProcessState.Pending\n      stdout.println(`start: ${state}`)\n\n      state++\n      stdout.println(`after ++ : ${state}`)\n\n      state++\n      stdout.println(`after ++ : ${state}`)\n\n      state++\n      stdout.println(`past last is set: ${state?}`)\n\n      state2 as ProcessState: ProcessState.Complete\n      state2--\n      state2--\n      state2--\n      stdout.println(`before first is set: ${state2?}`)","migrationContext":"Java: enum.ordinal() + 1 with manual bounds and ArrayIndexOutOfBoundsException risk. Kotlin: similar to Java. Rust: requires implementing Add or using strum crate. Python: no built-in next/previous on Enum. Go: iota integers with silent wrap-around. C#: cast to int and increment, no bounds checking. EK9: ++/-- with automatic boundary-to-unset semantics — the safest design in this list.","keywords":["++","--","ProcessState","enum","enumeration","next value","previous value","unset boundary"],"primaryTopics":["enum ++ --","boundary-to-unset","safe enum walking"],"typicalErrors":[{"error":"E50060","correct":"state++","incorrect":"state.next()","explanation":"EK9 enumerations do not have a next() method. The compiler cannot resolve the method call. Use the ++ operator to advance to the next declared value. See Q220 for the full enumeration navigation API. See ek9 -h E50060 for details."}],"companions":[]}
{"id":1263,"category":"Operators and Expressions","question":"If I have a String variable that might be unset, how can I conditionally assign a default value to it only when it has not been set yet?","url":"https://ek9.io/qa/QA1263.html","alternatePhrasings":["How do I assign a default to a String only when it is unset?","Show me the EK9 way to provide a fallback value for an unset String.","What is the operator that assigns only when the target is unset?","Give me a worked example of conditional default assignment on a String."],"answer":"EK9 has a dedicated operator for 'assign if unset': :=? (guarded assignment). It assigns the right-hand value to the variable ONLY if the variable is currently unset. If the variable already has a value, the assignment is silently skipped — the existing value is preserved.\n\nTHE OPERATOR\n  variable :=? defaultValue\n\nWORKED EXAMPLE\n  greeting as String: String()       // unset\n  greeting :=? \"Hello, World\"        // assigned, because greeting was unset\n  stdout.println(greeting)            // \"Hello, World\"\n\n  greeting :=? \"Goodbye\"              // SKIPPED, because greeting is now set\n  stdout.println(greeting)            // still \"Hello, World\"\n\nAFTER THE FIRST GUARDED ASSIGNMENT, THE VARIABLE IS SET AND ALL FUTURE :=? CALLS ARE NO-OPS. This is exactly what 'assign default if unset' should mean.\n\nWHY NOT IF + ASSIGNMENT?\nThe long-form equivalent works but is more verbose:\n  if not greeting?\n    greeting: \"Hello, World\"\n\nThe :=? operator collapses this into one line. It is also IMPOSSIBLE to forget the negation — there is only one direction the operator can go.\n\nNOT TO BE CONFUSED WITH OTHER OPERATORS\n- :=  is plain assignment (always assigns, overwrites)\n- :=? is guarded assignment (assigns only if unset)\n- <-  is declaration + first assignment (creates a new variable)\n- <?  is coalescing minimum (RETURNS the smaller value, completely different family)\n\nSee Q1264 for Integer guarded assignment. See Q1265 for Date. See Q1266 for custom record. See Q1226 for the contrast between :=? and <?.","ek9Example":"defines module qa.operators.guardedassignstring\n\n  defines function\n\n    //Sourced rather than a literal, so 'title' is genuinely already-set at runtime without\n    //the compiler being able to prove it and reject the ':=?' below as dead (E08095).\n    loadStoredTitle()\n      <- rtn as String: \"Untitled\"\n\n  defines program\n\n    GuardedAssignStringDemo()\n      stdout <- Stdout()\n\n      greeting as String: String()\n      greeting :=? \"Hello, World\"\n      stdout.println(`first :=? : ${greeting}`)\n\n      greeting :=? \"Goodbye\"\n      stdout.println(`second :=? : ${greeting}`)\n\n      // Variable that already has a value at declaration\n      title <- loadStoredTitle()\n      title :=? \"Default Title\"\n      stdout.println(`pre-set :=? : ${title}`)","migrationContext":"Java: if (variable == null) variable = default. Kotlin: variable = variable ?: default. Python: variable = variable or default (works but conflates falsy with None). Rust: variable = variable.unwrap_or(default). JavaScript: variable = variable ?? default (nullish coalescing assignment). EK9: variable :=? default — direct, single operator, only assigns when target is unset.","keywords":[":=?","String","conditional assignment","default value","fallback","guarded assignment","unset"],"primaryTopics":["String guarded assignment","default value pattern",":=? operator"],"typicalErrors":[{"error":"E50001","correct":"greeting :=? \"Hello, World\"","incorrect":"greeting <? \"Hello, World\"","explanation":":=? is guarded ASSIGNMENT (assign if unset). <? is coalescing MINIMUM (returns the smaller value). They are completely different operators and serve different purposes. See ek9 -h E50001 for details."}],"companions":[]}
{"id":1264,"category":"Operators and Expressions","question":"Walk me through using :=? on an Integer retry counter so that the counter only gets initialised on the first call and is preserved on subsequent calls.","url":"https://ek9.io/qa/QA1264.html","alternatePhrasings":["How do I initialise an Integer counter only once using :=?","Show me a worked example of guarded assignment for a retry counter.","Initialise an Integer to a default only when it has not been set.","How does :=? behave on Integer values that may already be set?"],"answer":"The :=? guarded assignment operator works on any type, including Integer. It assigns the right-hand value ONLY when the target is currently unset. This is exactly what you want for 'initialise once, never overwrite' patterns like retry counters, lazy caches, and configuration defaults.\n\nWORKED EXAMPLE: RETRY COUNTER\n  retryCount as Integer: Integer()    // unset\n\n  // First call: assigns 0 because retryCount was unset\n  retryCount :=? 0\n  stdout.println(`first :=? 0 -> ${retryCount}`)\n\n  // Second call: SKIPPED because retryCount is now set to 0\n  retryCount :=? 99\n  stdout.println(`second :=? 99 -> ${retryCount}`)\n\nThe second :=? does NOT change the value, even though 99 is different from 0. The operator only checks 'is the target unset?', not 'is the value different?'.\n\nINCREMENTING ON TOP OF GUARDED INIT\nA common pattern is to guard the initial value, then mutate freely afterwards:\n  attempts as Integer: Integer()\n  attempts :=? 0          // initialise once\n  attempts++              // 1\n  attempts++              // 2\n  attempts++              // 3\n  stdout.println(`attempts: ${attempts}`)\n\nThe first :=? guarantees the counter starts from 0 even if the variable was created without a default. After that, ++ takes over.\n\nWHY THIS IS SAFER THAN PLAIN :=\nUsing := blindly would overwrite the existing count and lose progress:\n  retryCount: 0     // Always overwrites — bad in restart-safe code\n  retryCount :=? 0  // Only sets if unset — safe to call multiple times\n\nFor any value that should be 'set once and stay', :=? is the right operator.\n\nSee Q1263 for String guarded assignment. See Q1265 for Date. See Q1266 for custom record. See Q1226 for the contrast between :=? and <?.","ek9Example":"defines module qa.operators.guardedassigninteger\n\n  defines program\n\n    GuardedAssignIntegerDemo()\n      stdout <- Stdout()\n\n      retryCount as Integer: Integer()\n      retryCount :=? 0\n      stdout.println(`first :=? 0 -> ${retryCount}`)\n\n      retryCount :=? 99\n      stdout.println(`second :=? 99 -> ${retryCount}`)\n\n      attempts as Integer: Integer()\n      attempts :=? 0\n      attempts++\n      attempts++\n      attempts++\n      stdout.println(`attempts after ++: ${attempts}`)","migrationContext":"Java: if (count == null) count = 0; — needs Integer wrapper, not int. Kotlin: count = count ?: 0. Rust: let count = count.unwrap_or(0); — but this rebinds, not modifies. Python: count = count if count is not None else 0. EK9: count :=? 0 — single operator, in-place, idempotent.","keywords":[":=?","Integer","default value","guarded assignment","initialise once","lazy init","retry counter"],"primaryTopics":["Integer guarded assignment","initialise-once pattern",":=? semantics"],"typicalErrors":[{"error":"E50060","correct":"retryCount :=? 0","incorrect":"retryCount.guardedSet(0)","explanation":"Integer does not have a guardedSet method. The :=? operator is the correct way to perform guarded assignment — it assigns only when the target is unset. See ek9 -h E50060 for details."}],"companions":[]}
{"id":1265,"category":"Operators and Expressions","question":"How can I record a 'first seen' Date that is set on the first observation and never overwritten on subsequent observations, using :=?","url":"https://ek9.io/qa/QA1265.html","alternatePhrasings":["Record a Date only on the first call without overwriting on later calls.","How do I implement 'first seen' semantics for a Date field?","Show me :=? on a Date variable.","Set a Date once and ignore later attempts to change it."],"answer":"The :=? guarded assignment operator is ideal for 'first seen' or 'first occurrence' patterns where you want to capture the FIRST value and ignore any later updates. It works on Date the same way it works on every other type — assign only if the target is unset.\n\nWORKED EXAMPLE: FIRST SEEN DATE\n  firstSeen as Date: Date()        // unset\n\n  // First observation: assigned because firstSeen was unset\n  firstSeen :=? 2025-03-10\n  stdout.println(`first :=? -> ${firstSeen}`)\n\n  // Later observations: SKIPPED because firstSeen is now set\n  firstSeen :=? 2025-04-15\n  firstSeen :=? 2025-05-20\n  stdout.println(`final firstSeen -> ${firstSeen}`)\n\nThe value remains 2025-03-10 throughout. The 'first seen' semantics are exactly what :=? provides — there is no need for an external 'has been seen' flag.\n\nWHY THIS IS USEFUL\n- Audit trails: record when a record was first created\n- Caches: store the first computed value, never recompute\n- Logs: capture the timestamp of the first error in a session\n- Statistics: track the start time of an aggregation window\n\nIN ALL THESE CASES, you want the FIRST value to win and all later values to be silently dropped. :=? does this in one line.\n\nALTERNATIVE WITHOUT :=?\n  if not firstSeen?\n    firstSeen: 2025-03-10\n\nThis works but is verbose. The :=? operator collapses the check and assignment into one symbol.\n\nSee Q1263 for String guarded assignment. See Q1264 for Integer. See Q1266 for custom record. See Q1226 for the contrast between :=? and <?.","ek9Example":"defines module qa.operators.guardedassigndate\n\n  defines program\n\n    GuardedAssignDateDemo()\n      stdout <- Stdout()\n\n      firstSeen as Date: Date()\n      firstSeen :=? 2025-03-10\n      stdout.println(`first :=? -> ${firstSeen}`)\n\n      firstSeen :=? 2025-04-15\n      firstSeen :=? 2025-05-20\n      stdout.println(`final firstSeen -> ${firstSeen}`)","migrationContext":"Java: if (firstSeen == null) firstSeen = LocalDate.of(2025, 3, 10). Kotlin: firstSeen = firstSeen ?: LocalDate.of(2025, 3, 10). Python: firstSeen = firstSeen or date(2025, 3, 10) — but this conflates falsy with None. Rust: firstSeen.get_or_insert(NaiveDate::from_ymd_opt(2025, 3, 10).unwrap()). EK9: firstSeen :=? 2025-03-10 — direct, single line, type-safe Date literal.","keywords":[":=?","Date","audit","first occurrence","first seen","guarded assignment","set once"],"primaryTopics":["Date guarded assignment","first-seen pattern","audit timestamp"],"typicalErrors":[{"error":"E50060","correct":"firstSeen :=? 2025-03-10","incorrect":"firstSeen.setIfAbsent(2025-03-10)","explanation":"Date does not have a setIfAbsent method. The :=? operator is the correct way to perform guarded assignment — it assigns only when the target is unset. See ek9 -h E50060 for details."}],"companions":[]}
{"id":1266,"category":"Operators and Expressions","question":"Show me how to use :=? on a custom class field — give me a worked example where a UserSession class has a sessionToken that is set on first login and never overwritten.","url":"https://ek9.io/qa/QA1266.html","alternatePhrasings":["How does :=? work inside a record method that sets a field once?","Give me a UserSession example using guarded assignment on a token field.","Use :=? on a custom record field for set-once semantics.","Show me a method that initialises a record field with :=? only when unset."],"answer":"The :=? guarded assignment works on class fields just as it does on local variables. It is the natural way to implement set-once fields without writing manual 'is this set?' checks. (For data-only carriers without behaviour, use a record — but records cannot have methods, so this example uses a class because it needs a captureToken method.)\n\nUSERSESSION CLASS WITH SET-ONCE TOKEN\n  UserSession\n    userName as String: String()\n    sessionToken as String: String()\n\n    UserSession()\n      -> userName as String\n      this.userName :=: userName\n\n    captureToken()\n      -> token as String\n      sessionToken :=? token\n\n    default operator ?\n\nThe captureToken method uses :=? on the field. The first call sets sessionToken. Every subsequent call is silently ignored — the original token is preserved.\n\nUSAGE\n  session <- UserSession(\"alice\")\n\n  // First login captures the token\n  session.captureToken(\"abc-123-first\")\n  stdout.println(`after first login: ${session.currentToken()}`)\n\n  // Subsequent logins do NOT replace the token\n  session.captureToken(\"xyz-456-second\")\n  session.captureToken(\"def-789-third\")\n  stdout.println(`after more logins: ${session.currentToken()}`)\n\nThe sessionToken stays as 'abc-123-first' regardless of how many times captureToken is called.\n\nWHY THIS IS BETTER THAN MANUAL CHECKS\nYou could write the same logic with an if-check:\n  captureToken()\n    -> token as String\n    if not sessionToken?\n      sessionToken: token\n\nThis works but is verbose and easy to get backwards. The :=? operator does the right thing in one symbol with no risk of missing the negation.\n\nWHEN TO USE GUARDED ASSIGNMENT ON FIELDS\n- Authentication tokens captured on first login\n- Origin URLs captured on first redirect\n- Created-at timestamps that should never change\n- Cached computed values that should be set once\n- Configuration that loads on first access\n\nIn all cases, the goal is 'first value wins, later values ignored'. :=? is the right tool.\n\nSee Q1263 for String guarded assignment. See Q1264 for Integer. See Q1265 for Date. See Q1226 for the contrast between :=? and <?.","ek9Example":"defines module qa.operators.guardedassignrecord\n\n  defines class\n\n    UserSession\n      userName as String: String()\n      sessionToken as String: String()\n\n      UserSession()\n        -> name as String\n        this.userName :=: name\n\n      captureToken()\n        -> token as String\n        sessionToken :=? token\n\n      currentToken() as pure\n        <- rtn as String: sessionToken\n\n      default operator ?\n\n  defines program\n\n    GuardedAssignRecordDemo()\n      stdout <- Stdout()\n\n      session <- UserSession(\"alice\")\n\n      session.captureToken(\"abc-123-first\")\n      stdout.println(`after first login: ${session.currentToken()}`)\n\n      session.captureToken(\"xyz-456-second\")\n      session.captureToken(\"def-789-third\")\n      stdout.println(`after more logins: ${session.currentToken()}`)","migrationContext":"Java: if (sessionToken == null) sessionToken = token; — verbose. Kotlin: sessionToken = sessionToken ?: token — also works, but reassigns. Rust: sessionToken.get_or_insert(token); — standard library helper. Python: if self.sessionToken is None: self.sessionToken = token. EK9: sessionToken :=? token — single operator, in-place, idempotent.","keywords":[":=?","UserSession","first value wins","guarded assignment","record field","session token","set once"],"primaryTopics":[":=? on record field","set-once field pattern","session token capture"],"typicalErrors":[{"error":"E50060","correct":"sessionToken :=? token","incorrect":"sessionToken.guardedSet(token)","explanation":"String does not have a guardedSet method. The :=? operator is the correct way to perform guarded assignment — it assigns only when the field is unset. See ek9 -h E50060 for details."}],"companions":[]}
{"id":1267,"category":"Control Flow Without break/continue/return","question":"How do I find the first matching item in a list without break or return in EK9?","url":"https://ek9.io/qa/QA1267.html","alternatePhrasings":["Write a find-first function using guarded assignment :=? in a for loop.","How does :=? help me stop at the first match in EK9?","Show me the EK9 pattern for returning the first item that satisfies a condition.","In Java I'd use break after finding the first match — what does EK9 use?"],"answer":"EK9 has no break or return. To find the first matching item, declare the return variable with a default (unset) value, then use the guarded assignment operator ':=?' inside the loop. ':=?' only assigns when the target is currently unset — so the first match sets it and all subsequent matches are ignored.\n\nPATTERN\n  findFirst()\n    -> items as List of String\n    -> searchTerm as String\n    <- rtn as String: String()\n    for item in items\n      if item contains searchTerm\n        rtn :=? item\n\nHOW IT WORKS\n1. rtn starts as String() — unset\n2. Loop iterates every item (no early exit)\n3. First match: rtn :=? item assigns because rtn is unset\n4. Second match: rtn :=? item is SKIPPED because rtn is already set\n5. After the loop, rtn holds the first match (or remains unset)\n\nCALLER PATTERN\nUse a guard expression to check if a match was found:\n  if found <- findFirst(names, searchFor)\n    stdout.println(found)\n  else\n    stdout.println(\"No match\")\n\nSee Q1239 for the stream pipeline alternative (filter + head). See Q985 for :=? basics.","ek9Example":"defines module qa.without.findfirstguard\n\n  defines function\n\n    findFirstContaining() as pure\n      ->\n        items as List of String\n        searchTerm as String\n      <- rtn as String: String()\n      for item in items\n        if item?\n          if item contains searchTerm\n            rtn :=? item\n\n  defines program\n\n    FindFirstDemo()\n      stdout <- Stdout()\n\n      names <- List() of String\n      names += \"Java\"\n      names += \"EK9-Language\"\n      names += \"Python\"\n      names += \"EK9-Compiler\"\n\n      searchFor <- \"EK9\"\n\n      if found <- findFirstContaining(names, searchFor)\n        stdout.println(`First match: ${found}`)\n      else\n        stdout.println(\"No match found\")\n\n      if noMatch <- findFirstContaining(names, \"Rust\")\n        stdout.println(`Found: ${noMatch}`)\n      else\n        stdout.println(\"No Rust match — correct\")","migrationContext":"Java: for-loop with break on first match. Python: next(x for x in items if condition, default). Kotlin: items.firstOrNull { condition }. Go: for-range with break. Rust: items.iter().find(|x| condition). EK9: rtn :=? match inside a for loop — no break, no return, the guard operator handles it.","keywords":[":=?","find-first","first match","guard","guarded assignment","loop","no break","no return"],"primaryTopics":[":=? guarded assignment in loops","find-first pattern","no break alternative"],"typicalErrors":[{"error":"E01072","correct":"        rtn :=? item","incorrect":"        return item","explanation":"EK9 has no return statement. Use a return variable declaration (rtn as Type: default) and :=? guarded assignment to set it once. See ek9 -h E01072 for details."},{"error":"E01070","correct":"        rtn :=? item","incorrect":"        break","explanation":"EK9 has no break statement. The :=? operator achieves the same effect — once rtn is set, further :=? assignments are ignored. See ek9 -h E01070 for details."}],"companions":[]}
{"id":1268,"category":"Generics","question":"Write a generic Pair class with two type parameters A and B that holds two values.","url":"https://ek9.io/qa/QA1268.html","alternatePhrasings":["Define a generic Pair of type (A, B) with a constructor, operator ?, and operator $.","How do I create a generic class with two type parameters in EK9?","Show me a Pair class that holds first and second values of different types.","In Java I'd write Pair<A, B> — what is the EK9 equivalent?"],"answer":"Generic classes with multiple type parameters use 'of type (A, B)' syntax. Key rules:\n\n1. MUST have a default constructor: 'default Pair() as pure'\n2. MUST have a parameterised constructor with both type params\n3. Constructors MUST be PUBLIC — private/protected on generics triggers E06060\n4. If any constructor is pure, ALL must be pure (E05190)\n5. Use 'operator $' (not 'override operator $') since there is no parent $ to override\n6. Use 'default operator ?' for auto-generated isSet semantics\n\nSYNTAX\n  Pair of type (A, B)\n    first as A?\n    second as B?\n    default Pair() as pure\n    Pair() as pure\n      ->\n        a as A\n        b as B\n      first :=? a\n      second :=? b\n    operator $ as pure\n      <- rtn as String: ...\n    default operator ?\n\nSee Q194 for single-parameter generics. See Q645 for why generic constructors must be public. See Q1243 for two-param Transformer example.","ek9Example":"defines module qa.genericsdeep.pairtwoparams\n\n  defines class\n\n    Pair of type (A, B)\n      first as A?\n      second as B?\n\n      Pair() as pure\n        first :=? A()\n        second :=? B()\n\n      Pair() as pure\n        ->\n          a as A\n          b as B\n        first :=? a\n        second :=? b\n\n      operator $ as pure\n        <- rtn as String: `(${$first}, ${$second})`\n\n      default operator ?\n\n  defines program\n\n    PairDemo()\n      stdout <- Stdout()\n\n      nameAge <- Pair(\"Alice\", 30)\n      stdout.println(`Pair: ${nameAge}`)\n\n      coordPair <- Pair(3.14, 2.72)\n      stdout.println(`Coordinates: ${coordPair}`)\n\n      if nameAge?\n        stdout.println(\"Pair is set\")","migrationContext":"Java: class Pair<A, B> { A first; B second; }. Kotlin: data class Pair<A, B>(val first: A, val second: B). Rust: struct Pair<A, B> { first: A, second: B }. Python: from typing import Generic, TypeVar. EK9: 'Pair of type (A, B)' with explicit constructors, 'default operator ?' and 'operator $'.","keywords":["A","B","constructor","default operator","generic","of type","pair","two type parameters"],"primaryTopics":["multi-parameter generics","Pair of type (A, B)","generic constructor rules"],"typicalErrors":[{"error":"E06060","correct":"      Pair() as pure","incorrect":"      private Pair() as pure","explanation":"Generic type constructors must be public so the type can be instantiated with any valid type parameter. Private or protected constructors prevent proper instantiation. See ek9 -h E06060 for details."},{"error":"E07110","correct":"      Pair() as pure\n        first :=? A()\n        second :=? B()","incorrect":"      Pair() as pure","explanation":"A non-abstract constructor must provide a body; removing the no-arg constructor's body leaves it with no implementation while it is not declared abstract. See ek9 -h E07110 for details."},{"error":"E05110","correct":"      operator $ as pure","incorrect":"      override operator $ as pure","explanation":"Generic classes have no parent $ operator to override. Use 'operator $' without 'override'. See ek9 -h E05110 for details."}],"companions":[]}
{"id":1269,"category":"Functions and Methods","question":"Write a higher-order function that takes a predicate and counts matching items in a list.","url":"https://ek9.io/qa/QA1269.html","alternatePhrasings":["How do I pass a function as a parameter in EK9?","Show me an abstract function used as a predicate with a dynamic function implementation.","Write a countMatching function that accepts a StringCheck predicate and a List of String.","In Java I'd use Predicate<String> — what is the EK9 pattern for higher-order functions?"],"answer":"EK9 uses abstract functions as the equivalent of functional interfaces. Define the abstract function with its signature, then create concrete implementations as DYNAMIC FUNCTIONS using the syntax '() is AbstractName as function'.\n\nABSTRACT FUNCTION (the predicate type)\n  StringCheck as abstract\n    -> text as String\n    <- rtn as Boolean?\n\nDYNAMIC FUNCTION (the implementation)\n  isLong <- () is StringCheck as function\n    rtn: length text > LONG_THRESHOLD\n\nNote: the dynamic function body uses the SAME parameter names as the abstract ('text', 'rtn'). Parameters are inherited, not redeclared.\n\nHIGHER-ORDER FUNCTION (accepts the predicate)\n  countMatching()\n    -> items as List of String, predicate as StringCheck\n    <- rtn as Integer: 0\n    for item in items\n      if predicate(item)\n        rtn++\n\nCall the predicate with normal function-call syntax: 'predicate(item)' returns Boolean.\n\nSee Q709 for built-in Predicate/Assessor types. See Q714 for Comparator. See Q1238 for strategy pattern using this approach.","ek9Example":"defines module qa.functionsandmethods.predicatehigherorder\n\n  defines constant\n\n    LONG_THRESHOLD <- 5\n\n  defines function\n\n    StringCheck as abstract\n      -> text as String\n      <- rtn as Boolean?\n\n    countMatching()\n      ->\n        items as List of String\n        predicate as StringCheck\n      <- rtn as Integer: 0\n      for item in items\n        if item?\n          matched <- predicate(item)\n          if matched\n            rtn++\n\n  defines program\n\n    PredicateDemo()\n      stdout <- Stdout()\n\n      words <- List() of String\n      words += \"hi\"\n      words += \"EK9-Language\"\n      words += \"compiler\"\n      words += \"go\"\n      words += \"abstract-function\"\n\n      isLong <- () is StringCheck as function\n        rtn: length text > LONG_THRESHOLD\n\n      longCount <- countMatching(words, isLong)\n      stdout.println(`Words longer than ${LONG_THRESHOLD}: ${longCount}`)","migrationContext":"Java: Predicate<String> isLong = s -> s.length() > 5; items.stream().filter(isLong).count(). Kotlin: val isLong: (String) -> Boolean = { it.length > 5 }; items.count(isLong). Python: is_long = lambda s: len(s) > 5; sum(1 for s in items if is_long(s)). EK9: abstract function + dynamic function '() is StringCheck as function' + higher-order parameter passing.","keywords":["abstract function","countMatching","delegate","dynamic function","function parameter","higher-order","predicate"],"primaryTopics":["higher-order functions","abstract function as predicate","dynamic function implementation"],"typicalErrors":[{"error":"E11064","correct":"        rtn: length text > LONG_THRESHOLD","incorrect":"        rtn: length text > 5","explanation":"Bare literal values in comparisons are not allowed — extract to a named constant. 'LONG_THRESHOLD <- 5' in a defines constant block. See ek9 -h E11064 for details."}],"companions":[]}
{"id":1270,"category":"Package Capability Security","question":"What makes an EK9 package publishable?","url":"https://ek9.io/qa/QA1270.html","alternatePhrasings":["When does EK9 enforce capability declarations on my package?","What conditions trigger the publishable package rules in EK9?","How do I know whether my package is treated as publishable by the compiler?","I'm preparing to publish a package — what must be in the defines package block?"],"answer":"An EK9 package is treated as 'publishable' — and therefore subject to compile-time capability enforcement — only when all FOUR of these are declared in the 'defines package' block:\n\n  1. publicAccess (true for the public EK9 repository, false for a private repository)\n  2. version (consumers need a specific version to reference)\n  3. license (consumers need to know their legal obligations)\n  4. capabilities (the list of gated system types the package uses)\n\nAll four must be present. A package that has publicAccess + version + license but omits the 'capabilities' block is NOT publishable in the enforcement sense — it compiles as if the feature did not exist, even when it uses gated types. This preserves backward compatibility for packages written before the capability feature landed and gives authors explicit control over when their module enters the enforcement regime.\n\nThe 'capabilities' declaration itself is the opt-in signal. Adding it is how an author says 'I am entering the supply chain and accepting the capability-review obligations.'\n\nTHE FOUR-CONDITION EXAMPLE\n  defines package\n    publicAccess <- true\n    version <- 1.0.0-0\n    description <- \"Simple publishable library\"\n    license <- \"MIT\"\n    capabilities <- [\"org.ek9.lang::Stdout\"]\n\nWHY EXPLICIT OPT-IN\nExisting published packages predate the capability feature. Forcing retroactive enforcement would break every existing library the day the feature landed. The fourth-condition rule means each package author decides when their module is ready to declare capabilities, upgrading on their own schedule rather than being blocked by a compiler change.\n\nSee Q1271 for how to declare capabilities once your package is publishable. See Q1275 for non-publishable packages. See Q1276 for pure-computation libraries. See Q1277 for error E12010.","ek9Example":"defines module qa.packagecapabilities.publishablefourconditions\n\n  defines package\n    version <- 1.0.0-0\n    description <- \"Demonstrates the four publishable conditions\"\n    license <- \"MIT\"\n    publicAccess <- true\n    capabilities <- [\"org.ek9.lang::Stdout\"]\n\n  defines function\n\n    showCapabilities()\n      stdout <- Stdout()\n      stdout.println(\"This module is publishable: all four conditions are set.\")\n      stdout.println(\"publicAccess + version + license + capabilities block\")","migrationContext":"Maven: package metadata is free-form; no declarative capability model. npm: package.json has no permission/capability concept; dependencies run arbitrary postinstall scripts. PyPI: no enforcement — package installs and runs arbitrary code. Cargo: same — no compile-time capability model. Go modules: no capability metadata at all. EK9: four-condition publishable rule activates compile-time capability enforcement per module, with explicit opt-in via the capabilities block.","keywords":["capabilities","four conditions","license","package","publicAccess","publishable","supply chain","version"],"primaryTopics":["publishable package","four-condition rule","capability opt-in"],"typicalErrors":[{"error":"E12010","correct":"    capabilities <- [\"org.ek9.lang::Stdout\"]","incorrect":"    capabilities <- [\"org.ek9.lang::TCP\"]","explanation":"A publishable package declared only TCP as a capability but its code uses Stdout. Each gated type used in the module's non-dev source must be covered by a matching entry in the capabilities list. See ek9 -h E12010 for details."}],"companions":[]}
{"id":1271,"category":"Package Capability Security","question":"How do I declare package capabilities in EK9?","url":"https://ek9.io/qa/QA1271.html","alternatePhrasings":["Show me the syntax for declaring capabilities in a defines package block.","Where do I put the capabilities list for a publishable package?","How do I tell the compiler my package uses Stdout or File?","What does a working capabilities declaration look like?"],"answer":"The 'capabilities' declaration sits inside the 'defines package' block alongside 'version', 'license', and the other package properties. Each entry is a fully-qualified gated type name using the 'org.ek9.lang::' module path.\n\nCOMPACT SINGLE-LINE FORM\n  capabilities <- [\"org.ek9.lang::Stdout\"]\n\nMULTI-LINE FORM (for multiple entries)\n  capabilities <- [\n    \"org.ek9.lang::Stdout\",\n    \"org.ek9.lang::File\",\n    \"org.ek9.lang::EnvVars\"\n    ]\n\nBOTH FORMS COMPILE IDENTICALLY. Use the compact form for a single capability, the multi-line form for readability when you have several.\n\nTHE FULL EXAMPLE\n  defines module my.publishable.library\n\n    defines package\n      publicAccess <- true\n      version <- 1.0.0-0\n      description <- \"My library\"\n      license <- \"MIT\"\n      capabilities <- [\"org.ek9.lang::Stdout\"]\n\n    defines function\n      sayHello()\n        stdout <- Stdout()\n        stdout.println(\"hello\")\n\nIMPORTANT RULES\n1. Capabilities list entries MUST be non-empty. EK9 list literals do not accept '[]'. If you want a pure-computation library, omit the 'capabilities' block entirely — see Q1276.\n2. Each gated type used in your non-dev source must be covered. The full gated type list appears in Q1273.\n3. Grouped types count as one capability. Declaring 'org.ek9.lang::TCP' covers TCPConnection, TCPHandler, and NetworkProperties automatically.\n4. Dev code (files under dev/) is exempt — see Q1278.\n\nSee Q1270 for the four-condition publishable rule. See Q1273 for the full gated types list. See Q1277 for what error E12010 means.","ek9Example":"defines module qa.packagecapabilities.declaresyntax\n\n  defines package\n    version <- 1.0.0-0\n    description <- \"Shows a publishable package declaring Stdout capability\"\n    license <- \"MIT\"\n    publicAccess <- true\n    capabilities <- [\"org.ek9.lang::Stdout\"]\n\n  defines function\n\n    sayHello()\n      stdout <- Stdout()\n      stdout.println(\"Hello from a publishable library\")","migrationContext":"Maven pom.xml: no capability concept. npm package.json: 'scripts' can run arbitrary code with no declared surface. Cargo.toml: no capability model. pyproject.toml: no enforcement. EK9: declarative 'capabilities <- [...]' list in 'defines package' is the single source of truth, enforced by the compiler at the module boundary.","keywords":["capabilities","declare","gated type","org.ek9.lang","package","syntax"],"primaryTopics":["capability declaration syntax","capabilities list","publishable package"],"typicalErrors":[{"error":"E12010","correct":"    capabilities <- [\"org.ek9.lang::Stdout\"]","incorrect":"    capabilities <- [\"org.ek9.lang::TCP\"]","explanation":"The module uses Stdout but the capabilities list declares TCP. TCP does not cover Stdout — they are independent gated types in different groups. Declare 'org.ek9.lang::Stdout' to match the actual gated type the code references. See ek9 -h E12010."}],"companions":[]}
{"id":1272,"category":"Package Capability Security","question":"Why does EK9 require capability declarations for publishable packages?","url":"https://ek9.io/qa/QA1272.html","alternatePhrasings":["What problem does EK9 capability security solve?","Why can't I just use any type in a published EK9 package?","What supply chain attack does EK9 capability enforcement prevent?","Explain the rationale for gated types in EK9."],"answer":"Every major software supply chain attack of the last decade shares the same kill chain: a compromised dependency quietly accesses system resources — reading environment variables, opening network connections, writing files — that its stated purpose does not require, and exfiltrates data. A JSON parser and a credential-stealing trojan look identical in their published metadata on npm, PyPI, Maven Central, or crates.io. Nothing in those ecosystems tells a consumer what a dependency will do at the system level.\n\nEK9 closes the gap structurally at the compiler. Every publishable package must declare the gated system types it uses in a 'capabilities' list. The compiler verifies the declarations match the actual code: you cannot use 'Stdout', 'TCP', 'File', or 'EnvVars' in a publishable module without declaring the matching capability. When a previously-pure package suddenly adds 'org.ek9.lang::TCP' to its capabilities list, that change is visible to every consumer the moment they fetch the new version from the repository — before a single line of it executes.\n\nTHE TWO CAPABILITIES EVERY ATTACK NEEDS\n  1. Reading secrets — EnvVars for API keys, File for credential files, Stdin for pipes\n  2. Exfiltration — TCP for network sockets, HTTPRequest for REST calls, File for disk\n\nEK9 gates both. A package that declares 'capabilities <- [\"org.ek9.lang::TCP\"]' has explicitly said 'I need network access' — a claim that consumers can evaluate against the package's advertised purpose. A package that declares no network capability literally cannot open a socket in its non-dev code; the compiler refuses to emit the bytecode.\n\nMOST PACKAGES NEED ZERO CAPABILITIES\nA JSON parser. A collection library. A string-manipulation utility. A mathematical package. None of them touch system resources — they should declare no capabilities, and the compiler enforces that pure-computation shape. When a legitimate library author updates their package they can be confident no accidental capability has crept in; when an attacker tries to insert one, the change is unmissable.\n\nTHE COST\nA few extra lines in your 'defines package' block when you write a publishable library. The occasional compile error when you start using a new gated type and forget to declare it. That's the entire developer tax.\n\nTHE BENEFIT\nStructural supply chain defence. No silent access escalation. No JSON parser that quietly reads your AWS credentials. No 'helper utility' that opens a reverse shell. The capability change is a compile error before it's a compromise.\n\nSee Q1270 for what makes a package publishable. See Q1271 for the capability declaration syntax. See Q1273 for the full gated types list. See Q1277 for error E12010.","ek9Example":"defines module qa.packagecapabilities.whyrationale\n\n  defines package\n    version <- 1.0.0-0\n    description <- \"Shows a pure computation library that needs no capabilities\"\n    license <- \"MIT\"\n    publicAccess <- true\n    capabilities <- [\"org.ek9.lang::Stdout\"]\n\n  defines function\n\n    explainWhy()\n      stdout <- Stdout()\n      stdout.println(\"Capability declarations make supply chain attacks structurally visible.\")\n      stdout.println(\"When a new version declares org.ek9.lang::TCP, every consumer sees it before it runs.\")","migrationContext":"npm (2015-2025): dozens of typosquatted and maintainer-hijack attacks; no capability model. PyPI (2022-2025): credential-stealing packages active for days before detection; no compile-time defence. Maven Central: no runtime permission enforcement beyond Java SecurityManager (deprecated). crates.io: safe Rust but no capability metadata. Go modules: no capability concept. EK9: compile-time capability enforcement at the module boundary — the compiler refuses to emit bytecode for undeclared gated type usage.","keywords":["attack","capability","defence","gated type","rationale","security","supply chain","why"],"primaryTopics":["supply chain security","capability rationale","attack prevention"],"typicalErrors":[],"companions":[]}
{"id":1273,"category":"Package Capability Security","question":"What is a 'gated type' in EK9 and which types are currently gated?","url":"https://ek9.io/qa/QA1273.html","alternatePhrasings":["Which EK9 types require a capability declaration in publishable packages?","Show me the list of gated types and their groupings.","Do I need to declare TCPConnection separately from TCP?","What are all the system types the compiler considers gated?"],"answer":"A GATED TYPE is a built-in EK9 type that represents access to a system resource — network sockets, filesystem, environment variables, standard streams, clocks, signals, and so on. Every gated type is defined in 'org.ek9.lang' and requires a matching entry in a publishable package's 'capabilities' list.\n\nThe full gated-type list (from GatedTypeMapping.java) groups related types under a single primary capability. Declaring the primary automatically covers the related types.\n\nNETWORK\n  org.ek9.lang::TCP           covers TCP, TCPConnection, TCPHandler\n  org.ek9.lang::UDP           covers UDP, UDPPacket\n  org.ek9.lang::NetworkProperties — covered by declaring either TCP or UDP\n\nHTTP\n  org.ek9.lang::HTTPRequest\n  org.ek9.lang::HTTPResponse\n\nFILESYSTEM\n  org.ek9.lang::File\n  org.ek9.lang::TextFile\n  org.ek9.lang::FileSystem\n  org.ek9.lang::FileSystemPath\n\nSTANDARD STREAMS\n  org.ek9.lang::Stdin\n  org.ek9.lang::Stdout\n  org.ek9.lang::Stderr\n\nSYSTEM AND ENVIRONMENT\n  org.ek9.lang::EnvVars\n  org.ek9.lang::OS\n  org.ek9.lang::Signals\n\nTIME AND CLOCKS\n  org.ek9.lang::Clock\n  org.ek9.lang::SystemClock\n\nSENSITIVE DATA\n  org.ek9.lang::Sensitive\n\nGROUPING RULES\nDeclaring the primary type covers all associated types in its group. For example, 'capabilities <- [\"org.ek9.lang::TCP\"]' is sufficient to use TCPConnection, TCPHandler, and NetworkProperties in the module. You do not need to list each related type individually.\n\nNetworkProperties is a special case: it is shared between TCP and UDP, so declaring either one covers it.\n\nMULTI-CAPABILITY EXAMPLE\nA package that uses HTTP, writes to stdout, and reads environment variables declares three capabilities:\n  capabilities <- [\n    \"org.ek9.lang::HTTPRequest\",\n    \"org.ek9.lang::HTTPResponse\",\n    \"org.ek9.lang::Stdout\",\n    \"org.ek9.lang::EnvVars\"\n    ]\n\nWHAT IS NOT GATED\nEverything else — String, Integer, Float, Boolean, Date, DateTime, List, Dict, Optional, Result, and every user-defined type. Pure-computation code uses only these and needs no capability declarations.\n\nSee Q1271 for declaration syntax. See Q1272 for the rationale. See Q1277 for error E12010. See Q1276 for pure-computation libraries that need no capabilities.","ek9Example":"defines module qa.packagecapabilities.gatedtypeslist\n\n  defines package\n    version <- 1.0.0-0\n    description <- \"Publishable package demonstrating multiple gated type declarations\"\n    license <- \"MIT\"\n    publicAccess <- true\n    capabilities <- [\"org.ek9.lang::Stdout\", \"org.ek9.lang::Stderr\"]\n\n  defines function\n\n    useBothStreams()\n      stdout <- Stdout()\n      stderr <- Stderr()\n      stdout.println(\"standard output goes here\")\n      stderr.println(\"error output goes here\")","migrationContext":"Java SecurityManager (deprecated): runtime permission checks via AccessController; no compile-time visibility. .NET Code Access Security (deprecated): similar runtime-only model. Rust: no standard-library capability model; unsafe blocks flag memory safety but not I/O. Go: no capability metadata. Haskell IO monad: types carry effects but no package-level declaration. EK9: compile-time gated-type list with grouped related types, each declared explicitly in the package block.","keywords":["EnvVars","File","Stdout","TCP","gated type","grouping","list","org.ek9.lang"],"primaryTopics":["gated types","capability groupings","system resource types"],"typicalErrors":[{"error":"E12010","correct":"    capabilities <- [\"org.ek9.lang::Stdout\", \"org.ek9.lang::Stderr\"]","incorrect":"    capabilities <- [\"org.ek9.lang::Stdout\"]","explanation":"The module uses both Stdout and Stderr. Declaring only Stdout leaves the Stderr usage uncovered and fires E12010. Each gated type used must have a matching entry — declaring one gated type does not cover any other. See ek9 -h E12010."}],"companions":[]}
{"id":1274,"category":"Package Capability Security","question":"Does my EK9 package inherit its dependencies' capability declarations?","url":"https://ek9.io/qa/QA1274.html","alternatePhrasings":["If I depend on a package that uses Stdout, do I also need to declare Stdout?","Does EK9 check that my dependencies' capabilities are covered in my package?","Are capability declarations transitive through dependencies in EK9?","When I call a library function that uses TCP internally, do I need to declare TCP in my own package?"],"answer":"No — EK9 capability enforcement is STRICTLY PER-MODULE. Each publishable package declares only its own capabilities, and the compiler verifies those declarations against the package's own non-dev source code. Depending on another package does not inherit that package's capabilities, and the compiler performs no cross-module capability reasoning.\n\nTHE RULE\nYour 'capabilities' list must cover every gated type that appears in YOUR OWN code — meaning every type your source file directly mentions, not types that dependencies happen to use internally. If you call a library function that returns an Integer, the type your code mentions is Integer; whether the function uses Stdout internally is the library's business, not yours.\n\nTHE ONE CASE WHERE IT APPLIES\nIf a dependency's public API takes or returns a gated type — for example, a function signature like 'makeConnection() as pure <- rtn as TCPConnection' — and your code calls that function and stores the result, then YOUR code mentions TCPConnection. TCPConnection is grouped under TCP, so your package must declare 'org.ek9.lang::TCP'. This is not transitive inheritance; it is the normal per-module rule applied to a cross-module reference: the type appears in your AST, so you must declare it.\n\nWHY PER-MODULE, NOT TRANSITIVE\nThe compiler's responsibility stays tight and local: one question per module, no cross-module reasoning, no acknowledgment maps to maintain. Visibility into what dependencies declare is a repository/tooling concern — you can inspect any dependency's 'defines package' block in its source to see its capabilities, and the EK9 repository surfaces capability changes between versions for human review at upgrade time.\n\nThe trade-off is that a dependency silently adding a new capability does not automatically break consumer builds. Instead, the upgrade workflow handles it: you review what's changed when you pull the new version, not when you rebuild.\n\nEXAMPLE\n  // Library: declares Stdout, uses it internally, exports pure functions\n  defines module my.library\n    defines package\n      publicAccess <- true\n      version <- 1.0.0-0\n      license <- \"MIT\"\n      capabilities <- [\"org.ek9.lang::Stdout\"]\n    defines function\n      compute() as pure\n        -> n as Integer\n        <- rtn as Integer: n * 2\n      internalLog()\n        stdout <- Stdout()\n        stdout.println(\"log\")\n\n  // Consumer: calls compute(), never references Stdout itself\n  // Only needs its own capabilities — nothing to inherit from my.library\n  defines module my.consumer\n    references\n      my.library::compute\n    defines package\n      publicAccess <- true\n      version <- 1.0.0-0\n      license <- \"MIT\"\n      capabilities <- [\"org.ek9.lang::Stderr\"]\n    defines function\n      useCompute()\n        stderr <- Stderr()\n        result <- compute(21)\n        stderr.println(`Got: ${result}`)\n\nThe consumer does NOT declare Stdout even though 'my.library' uses it internally. Only the types the consumer's own source mentions (Stderr, Integer, String) matter for its own capability check.\n\nSee Q1270 for the four-condition publishable rule. See Q1271 for declaration syntax. See Q1273 for the gated types list.","ek9Example":"defines module qa.packagecapabilities.permoduleenforcement\n\n  defines package\n    version <- 1.0.0-0\n    description <- \"Demonstrates per-module capability enforcement\"\n    license <- \"MIT\"\n    publicAccess <- true\n    capabilities <- [\"org.ek9.lang::Stdout\"]\n\n  defines function\n\n    pureHelper() as pure\n      -> n as Integer\n      <- rtn as Integer: n * 2\n\n    usesOwnCapability()\n      stdout <- Stdout()\n      result <- pureHelper(21)\n      stdout.println(`Result: ${result}`)","migrationContext":"Maven transitive dependencies: resolved automatically with no capability awareness. npm audit: flags CVEs but not capability changes. Cargo features: compile-time but orthogonal to permissions. Java jlink modules: declare service use but not system access. EK9: per-module capability enforcement, no transitive propagation — each module stands on its own declarations.","keywords":["capability","dependency","enforcement","inheritance","per-module","reference","transitive"],"primaryTopics":["per-module enforcement","non-transitive capabilities","cross-module references"],"typicalErrors":[{"error":"E12010","correct":"    capabilities <- [\"org.ek9.lang::Stdout\"]","incorrect":"    capabilities <- [\"org.ek9.lang::Stderr\"]","explanation":"This module's code uses Stdout directly. Declaring Stderr instead does not cover Stdout — each gated type is checked independently against the module's own source. This illustrates per-module enforcement: even if a dependency declared Stdout, the consumer must still declare its own. See ek9 -h E12010."}],"companions":[]}
{"id":1275,"category":"Package Capability Security","question":"Do non-publishable EK9 packages need capability declarations?","url":"https://ek9.io/qa/QA1275.html","alternatePhrasings":["Can I use Stdout and File freely in a local project that I'm not publishing?","When is my package exempt from capability enforcement?","Does the capability rule apply to scripts and local experiments?","What happens if my package block has only a description?"],"answer":"No — non-publishable packages are entirely exempt from capability enforcement. You can freely use Stdout, File, TCP, EnvVars, or any other gated type without declaring them in a 'capabilities' block.\n\nA package is NON-PUBLISHABLE when any of the four publishable conditions is missing:\n  - publicAccess is not declared\n  - version is not declared\n  - license is not declared\n  - capabilities block is not present\n\nWhen any of these is missing, the compiler does not activate capability enforcement for the module. This matches the reality that local scripts, developer experiments, file-organisation wrappers, and pure dependency-management 'defines package' blocks are not entering a supply chain. The developer's own machine, the developer's own risk.\n\nCOMMON NON-PUBLISHABLE SHAPES\n\n1. Description only — just naming the package for organisation:\n  defines package\n    description <- \"My local project\"\n\n2. File organisation with applyStandardIncludes:\n  defines package\n    description <- \"Collected local utilities\"\n    applyStandardIncludes <- true\n\n3. Dependency management without publishing intent:\n  defines package\n    description <- \"Pulls in external libraries for local use\"\n    deps <- {\n      \"ekopen.math.simple.constants\": \"2.3.14-0\"\n      }\n\nAll three compile freely and may use any gated types in their source code. The compiler does not run the capability check because 'publishable' evaluates to false.\n\nTHE TRANSITION POINT\nThe moment you declare publicAccess, version, license, AND a capabilities block, your package opts into enforcement. That is the single moment a module 'enters the supply chain' in EK9's model, and the only moment the compiler starts asking 'is this gated type covered by a declared capability?' Until then, you are writing ordinary code on your own machine and nothing is gated.\n\nWHY THE EXEMPTION EXISTS\nForcing capability declarations on every package — including scripts, experiments, and throwaway builds — would create enormous friction for zero security benefit. The attacks capability security is designed to stop are supply-chain attacks: compromised dependencies that end up in other people's builds. Code that never leaves your machine cannot be a supply chain attack. The exemption respects that reality.\n\nSee Q1270 for the four publishable conditions. See Q1276 for pure-computation publishable libraries. See Q1278 for the dev/ test exemption within publishable packages.","ek9Example":"defines module qa.packagecapabilities.nonpublishable\n\n  defines package\n    description <- \"Non-publishable local package — capabilities not required\"\n\n  defines function\n\n    freelyUseStdout()\n      stdout <- Stdout()\n      stdout.println(\"This module has no publicAccess, no version, no license.\")\n      stdout.println(\"It is not publishable, so capability enforcement is not active.\")\n\n    freelyUseStderr()\n      stderr <- Stderr()\n      stderr.println(\"Stderr is also freely usable here.\")","migrationContext":"Python virtualenv: local experiments run arbitrary code; no package-level policy. Node.js local package.json: same. Rust workspaces: local members can use any std APIs. Java local projects: no security manager by default. EK9: non-publishable packages explicitly exempt — capability enforcement activates only at the publishable threshold, which is deliberately opt-in.","keywords":["dependency management","exemption","experiment","local","non-publishable","publishable","script"],"primaryTopics":["non-publishable exemption","local package","no-capability required"],"typicalErrors":[],"companions":[]}
{"id":1276,"category":"Package Capability Security","question":"How do I write a pure-computation publishable library in EK9?","url":"https://ek9.io/qa/QA1276.html","alternatePhrasings":["How do I publish a library that uses no system resources?","What is the idiom for a JSON parser or maths library that doesn't need capabilities?","Can I publish a package without declaring any capabilities?","How do I say 'this library is pure computation' in the package block?"],"answer":"For a publishable library that touches no system resources — a JSON parser, a string-manipulation utility, a collection library, a mathematical package — the idiom is simple: OMIT THE 'capabilities' BLOCK ENTIRELY.\n\nWithout a 'capabilities' declaration, the fourth publishable condition is not met, so the compiler does not activate capability enforcement for the module. The package compiles exactly as it would have before the capability feature existed. Users pulling the library can see from its package block that no 'capabilities' declaration is present and therefore the library author has not entered the capability enforcement regime — which is fine, because the library genuinely uses no gated types.\n\nTHE PURE COMPUTATION PATTERN\n  defines module com.example.jsonparser\n\n    defines package\n      publicAccess <- true\n      version <- 2.1.0-0\n      description <- \"Pure JSON parsing library\"\n      license <- \"MIT\"\n      //No 'capabilities' block: pure computation, no gated types used.\n\n      deps <- {\n        \"org.ek9.collections\": \"1.0.0-0\"\n        }\n\n    defines function\n      parseJson() as pure\n        -> input as String\n        <- rtn as JSON: JSON()\n        //Pure parsing logic — only String, JSON, and collection types\n\nWHY NOT USE 'capabilities <- []'?\nEK9 list literals currently require at least one element, so 'capabilities <- []' does not parse. The design intent of an empty list — 'this package is explicitly pure computation' — is achieved instead by omitting the block, which has the same practical effect: no enforcement, no gated types needed, no supply-chain risk.\n\nWHEN TO ADD A 'capabilities' BLOCK\nAs soon as your library legitimately needs to touch a system resource — for example, a networking library that needs TCP, or a logging utility that needs Stdout — add the 'capabilities' block with the required entries. The moment the block exists, your module enters enforcement: every gated type in its code must be covered.\n\nWHAT CONSUMERS SEE\nA consumer who fetches a pure-computation library sees its 'defines package' block and immediately knows (a) the library is published and versioned and (b) the author has not declared any capabilities — meaning they have not opted into enforcement. A careful consumer can verify by reading the library's source that no gated types are actually used. A pure-computation library has an audit story that matches its claim.\n\nSee Q1270 for the four-condition publishable rule. See Q1271 for declaration syntax. See Q1272 for the supply chain rationale. See Q1275 for non-publishable packages (which also skip enforcement but for a different reason — they aren't published at all).","ek9Example":"defines module qa.packagecapabilities.purecomputation\n\n  defines package\n    publicAccess <- true\n    version <- 1.0.0-0\n    description <- \"Pure computation library — no capabilities block declared\"\n    license <- \"MIT\"\n\n  defines function\n\n    doubleValue() as pure\n      -> input as Integer\n      <- rtn as Integer: input * 2\n\n    concatenate() as pure\n      ->\n        prefix as String\n        suffix as String\n      <- rtn as String: `${prefix}${suffix}`\n\n    isPositive() as pure\n      -> candidate as Integer\n      <- rtn as Boolean: candidate > 0","migrationContext":"Maven/pom.xml: no way to declare 'this library is pure computation.' npm: no equivalent. Cargo: no equivalent. Haskell: pure functions are tracked by the type system but package-level purity is not distinguished. EK9: pure-computation publishable libraries omit the capabilities block entirely; audit is trivial because the absence of the declaration matches the absence of gated type usage.","keywords":["JSON","computation","library","mathematical","no capabilities","omit","parser","pure"],"primaryTopics":["pure computation","library without capabilities","omitting the capabilities block"],"typicalErrors":[],"companions":[]}
{"id":1277,"category":"Package Capability Security","question":"What does EK9 error E12010 mean and how do I fix it?","url":"https://ek9.io/qa/QA1277.html","alternatePhrasings":["I'm getting E12010 UNDECLARED_CAPABILITY — what does this error mean?","The compiler says a type requires a capability declaration — how do I add it?","How do I fix 'type requires declared package capability' errors?","What triggers E12010 in EK9?"],"answer":"E12010 fires when your publishable package uses a gated system type but has not declared the corresponding capability. The error message looks like:\n\n  Error : E12010: 'Stdout' on line 14 position 16: 'Stdout' requires capability 'org.ek9.lang::Stdout': type requires declared package capability\n\nThe fix is always the same: add the required capability to the 'capabilities' list in your 'defines package' block. The error message tells you exactly which capability entry is missing.\n\nTHE SCENARIO\nYour package meets all four publishable conditions (publicAccess + version + license + capabilities block), so capability enforcement is active. Somewhere in your non-dev source you reference a type that is gated — Stdout, File, TCP, EnvVars, or any other — and your capabilities list does not include it (or its primary-type grouping).\n\nTHE FIX\nAdd the required entry to the capabilities list. For Stdout:\n  capabilities <- [\"org.ek9.lang::Stdout\"]\n\nFor multiple gated types, use the multi-line form:\n  capabilities <- [\n    \"org.ek9.lang::Stdout\",\n    \"org.ek9.lang::Stderr\",\n    \"org.ek9.lang::File\"\n    ]\n\nFor grouped types, declare the primary: 'org.ek9.lang::TCP' covers TCPConnection, TCPHandler, and NetworkProperties automatically.\n\nCOMMON TRIGGERS\n1. Using Stdout/Stderr/Stdin for basic I/O in a publishable library without declaring the stream capabilities.\n2. Reading environment variables via EnvVars without declaring 'org.ek9.lang::EnvVars'.\n3. Declaring TCPConnection as a field type instead of declaring the primary TCP capability.\n4. Using File for persistence without 'org.ek9.lang::File' in the list.\n5. Declaring some capabilities but missing one — each gated type needs its own entry (or its primary grouping).\n\nEACH GATED TYPE COUNTS INDEPENDENTLY\nDeclaring Stdout does not cover Stderr. Declaring TCP does not cover File. The compiler reports a separate E12010 for each undeclared gated type it finds, so fixing one does not mask the others.\n\nALTERNATIVES TO ADDING A CAPABILITY\n1. If the code that uses the gated type is actually test or fixture code, move it under a 'dev/' directory — dev code is exempt from capability enforcement (see Q1278).\n2. If the package does not actually need to be publishable yet, remove one of the publishable conditions (publicAccess, version, license, or the capabilities block itself) and the enforcement will deactivate. This is the right move for local packages and experiments (see Q1275).\n3. If the code shouldn't use the gated type at all — maybe an accidental Stdout in a pure-computation library — remove the usage instead of declaring the capability. The clean declaration 'this library needs no capabilities' is preserved (see Q1276).\n\nSee Q1270 for publishable conditions. See Q1271 for declaration syntax. See Q1273 for the full gated types list.","ek9Example":"defines module qa.packagecapabilities.e12010fix\n\n  defines package\n    version <- 1.0.0-0\n    description <- \"Correctly declares all capabilities used — E12010 would have fired without these\"\n    license <- \"MIT\"\n    publicAccess <- true\n    capabilities <- [\"org.ek9.lang::Stdout\", \"org.ek9.lang::Stderr\"]\n\n  defines function\n\n    writeInfo()\n      stdout <- Stdout()\n      stdout.println(\"Info message on stdout\")\n\n    writeError()\n      stderr <- Stderr()\n      stderr.println(\"Error message on stderr\")","migrationContext":"Java runtime SecurityException: similar in spirit but runtime-only, deprecated, and covers a narrower surface. .NET permissions: also runtime, similarly deprecated. Rust compile-time enforcement: limited to memory safety, not I/O capability. EK9 E12010: compile-time gated type check with immediate fix guidance from the rich error message and QA cross-references.","keywords":["E12010","UNDECLARED_CAPABILITY","error","fix","gated type","missing capability"],"primaryTopics":["E12010 error","fixing capability errors","compiler enforcement"],"typicalErrors":[{"error":"E12010","correct":"    capabilities <- [\"org.ek9.lang::Stdout\", \"org.ek9.lang::Stderr\"]","incorrect":"    capabilities <- [\"org.ek9.lang::Stdout\"]","explanation":"The module uses both Stdout and Stderr. Declaring only Stdout leaves the Stderr usage uncovered — the compiler reports a separate E12010 for each undeclared gated type. Add 'org.ek9.lang::Stderr' to cover it. See ek9 -h E12010."}],"companions":[]}
{"id":1278,"category":"Package Capability Security","question":"Are EK9 dev/ test files exempt from capability enforcement?","url":"https://ek9.io/qa/QA1278.html","alternatePhrasings":["Do my test files need capability declarations?","Can test code use Stdout and File freely without adding capabilities?","Why are dev/ directories treated specially for capability checks?","Where do I put test fixtures that use gated types?"],"answer":"Yes — code under a 'dev/' directory is EXEMPT from capability enforcement. Test files, fixtures, and development-time utilities living under dev/ may freely use any gated type without declaring the matching capability in the package's 'capabilities' list.\n\nTHE RULE\nThe capability check in 'CapabilityTypeReferenceOrError.checkNode' early-returns when 'symbolsAndScopes.isDevSource()' is true. Dev sources are identified by the directory structure: anything under 'dev/' is flagged as dev source and skipped during capability enforcement.\n\nPRACTICAL EFFECT\n  my.publishable.library/\n    main.ek9                // publishable, subject to capability rules\n    helper.ek9              // publishable, subject to capability rules\n    dev/\n      tests.ek9             // EXEMPT — can use Stdout, File, EnvVars freely\n      testFixtures.ek9      // EXEMPT\n\nThe main.ek9 and helper.ek9 must declare every gated type they use. tests.ek9 can import, call, assert, print to stdout, read environment variables, open test files — whatever the tests need — without any of those types appearing in the capabilities list.\n\nWHY THE EXEMPTION EXISTS\nTests naturally need access to system resources in ways that production code does not:\n  - Reading fixture files from the filesystem\n  - Mocking environment variables\n  - Capturing stdout/stderr to verify output\n  - Constructing instances of types that wrap TCP, File, or Sensitive data\n\nIf dev/ code had to declare every gated type used in test fixtures, test suites would force the production 'capabilities' list to grow to include every test-only dependency. That would defeat the purpose of the rule: the production code's capability declaration would no longer be a trustworthy claim about what the library does at runtime — it would be contaminated by test-fixture requirements.\n\nSeparating test code into 'dev/' and exempting it means:\n  - Production capability declarations stay clean and accurate\n  - Tests remain free to exercise edge cases without ceremony\n  - Consumers auditing a library's capabilities see only what the production code needs\n\nWHAT COUNTS AS DEV CODE\nAny file whose path includes a 'dev' directory segment (at the package root or nested). The EK9 project layout convention is to keep tests under 'dev/' directly inside the package directory, alongside the main source files.\n\nWHAT IS NOT EXEMPT\n  - Files NOT under dev/ — regardless of whether they contain tests, fixtures, or development-time code. If a file is in the main source tree and the package is publishable, it goes through capability enforcement.\n  - Files under dev/ in non-publishable packages — but this doesn't matter, because non-publishable packages are entirely exempt regardless of directory (see Q1275).\n\nHOW THIS INTERACTS WITH THE TEST RUNNER\nEK9's built-in test runner ('ek9 -t') discovers and executes tests in the 'dev/' directory. The test runner itself is not bound by the library's capability declarations — it operates at a higher trust level because it is explicitly invoked by the developer to exercise their own code, not by a downstream consumer pulling a dependency.\n\nSee Q1270 for the four-condition publishable rule. See Q1275 for non-publishable exemption. See Q1271 for capability declaration syntax.","ek9Example":"defines module qa.packagecapabilities.devexemption\n\n  defines package\n    version <- 1.0.0-0\n    description <- \"Shows main code declaring only what it uses; dev/ tests would be exempt\"\n    license <- \"MIT\"\n    publicAccess <- true\n    capabilities <- [\"org.ek9.lang::Stdout\"]\n\n  defines function\n\n    productionHelper()\n      stdout <- Stdout()\n      stdout.println(\"This is production code — must declare its own capabilities\")","migrationContext":"Maven/Gradle test scope: dependencies in 'test' scope are not packaged with the production artifact but have no runtime capability implications. npm devDependencies: similar, only controls what's installed. Python dev-dependencies: same pattern. EK9: dev/ directory is a structural boundary for capability enforcement — dev code is not part of the published supply chain and is exempt from the rule.","keywords":["dev","development","exemption","fixture","test","test runner"],"primaryTopics":["dev code exemption","test files","development-time code"],"typicalErrors":[],"companions":[]}
{"id":1279,"category":"Package Capability Security","question":"How should an AI assistant handle EK9 package capability changes?","url":"https://ek9.io/qa/QA1279.html","alternatePhrasings":["Can ek9 -ai autonomously approve a new capability declaration?","Should an AI coder add capabilities to a package without asking?","What is the human-in-the-loop requirement for capability changes?","Why can't an AI just fix E12010 by adding the required capability?"],"answer":"An AI assistant MUST NOT autonomously approve or add capability declarations when working with publishable EK9 packages. Every change to a package's 'capabilities' list — whether adding a new entry, modifying an existing one, or introducing the block for the first time — requires human review and explicit human approval before it is committed.\n\nTHE RULE\nCapability declarations are the supply chain trust boundary. When a publishable package gains a new capability, every downstream consumer is implicitly accepting increased risk: the dependency can now touch a system resource it previously could not. The value of the capability system depends on that decision being made by a human who understands the trade-off, not by an automated agent that just wanted E12010 to go away.\n\nAn AI that mechanically adds 'org.ek9.lang::TCP' to a package's capabilities list because the compiler complained has performed a silent supply chain escalation. A human reviewer would ask 'why does this JSON parser suddenly need network access?' The AI does not.\n\nTHE CORRECT AI BEHAVIOUR\n\n1. WHEN E12010 FIRES in non-publishable or dev code:\n   - The AI may freely fix the error by adjusting the capabilities list, because non-publishable packages and dev sources are not part of the supply chain.\n\n2. WHEN E12010 FIRES in a publishable package:\n   - The AI must STOP and escalate to the human. The appropriate response is something like:\n     'The publishable package needs a new capability declaration to compile. Adding capabilities to a publishable package is a supply chain decision I should not make autonomously. Please review whether this library should actually use org.ek9.lang::TCP, and if so, approve the change.'\n   - The AI may propose the fix as a diff for human review, but must not apply it.\n\n3. WHEN AN EXISTING CAPABILITY WOULD BE REMOVED:\n   - Removing a capability is also a decision that affects trust but in the opposite direction. An AI may suggest removal when it is reducing scope, but the human should confirm — especially for libraries that are already published.\n\n4. WHEN THE CAPABILITY IS CLEARLY ACCIDENTAL:\n   - If the AI can see that a Stdout usage is leftover debug code in a library that is meant to be pure computation, the appropriate fix is to REMOVE the debug code, not to add the capability. The AI should propose the removal and explain the reasoning.\n\nWHY THIS MATTERS FOR THE ek9 -ai TOOL\nThe 'ek9 -ai' framework is designed as a delegated-agent development loop — a local AI coder handling routine tasks with Frontier escalation for complex ones. Capability declarations are one of a small number of decisions where the Frontier (and ultimately the human) must be in the loop even for otherwise-trivial changes. The AI's value proposition is in writing correct idiomatic EK9 code; it is not to decide what system resources a published library is allowed to access.\n\nA SLEEPER AGENT SCENARIO\nConsider: an AI assistant is asked to fix a failing test in a published library. The fix happens to involve code that touches a new gated type. If the AI autonomously adds the capability, commits the change, and publishes the new version, the library has gained a new system access point without any human noticing. Every consumer pulling the new version inherits the escalation silently. This is exactly the attack vector that capability security is designed to prevent. AI automation would reintroduce it.\n\nTHE CORRECT RESPONSE is for the AI to surface the decision: 'This change would require declaring org.ek9.lang::TCP as a new capability. This is a supply chain escalation. Please review.' The human approves or rejects. The audit trail survives.\n\nSee Q1270 for the publishable rule. See Q1272 for the supply chain rationale.","ek9Example":"defines module qa.packagecapabilities.aihumanreview\n\n  defines package\n    version <- 1.0.0-0\n    description <- \"Publishable package — AI must not autonomously modify its capabilities list\"\n    license <- \"MIT\"\n    publicAccess <- true\n    capabilities <- [\"org.ek9.lang::Stdout\"]\n\n  defines function\n\n    safeToMaintain()\n      stdout <- Stdout()\n      stdout.println(\"An AI may edit this function freely — the capabilities list is unchanged.\")\n\n    alsoSafe() as pure\n      -> n as Integer\n      <- rtn as Integer: n + 1","migrationContext":"GitHub Copilot, Cursor, Claude Code, Amazon Q: none of these distinguish supply-chain-critical changes from routine edits. Any AI that can write code can silently escalate permissions in a package manifest. EK9: capability changes are a designated human-review boundary; the ek9 -ai framework treats them as non-delegable decisions regardless of how routine the surrounding code change appears.","keywords":["AI assistant","autonomous","capability","ek9 -ai","escalation","human review","sleeper agent","supply chain"],"primaryTopics":["AI human-in-the-loop","capability change review","supply chain safety"],"typicalErrors":[],"companions":[]}
{"id":1282,"category":"Classes and OOP","question":"What does EK9 error E04050 mean when I declare a class field and how do I fix it?","url":"https://ek9.io/qa/QA1282.html","alternatePhrasings":["I'm getting E04050 'not expecting complex expression' on a class field — what's wrong?","Why does 'names <- List() of String?' fail to compile in a class body?","How do I initialize a List field in an EK9 class without E04050?","The compiler says my field declaration needs a simple aggregate — how do I fix it?"],"answer":"E04050 fires when you use the '<-' declaration form for a class field with an expression the compiler cannot resolve to a simple type during early phase processing. In practice the usual cause is writing the is-set operator '?' at the end of a parameterised constructor call, like 'names <- List() of String?'. That is NOT a nullable-type marker in EK9 — it is the is-set operator being invoked on the newly-created List, which produces a Boolean, which cannot be the type of a List field. The compiler reports E04050 because type inference cannot reconcile 'List of String' with 'Boolean'.\n\nTHE FIX\nDrop the trailing '?' and use the simple parameterised constructor form:\n\n  names <- List() of String\n\nThis declares a class field 'names' of type 'List of String' and initialises it to an empty list. The List type is always 'set' when created, so you do not need an is-set check on it.\n\nWHY THIS CATCHES PEOPLE\nC#, Kotlin, TypeScript, and Swift all use a trailing '?' on a type to mean 'nullable'. EK9 has no null. '?' in EK9 is the is-set operator — it is a unary postfix method that returns a Boolean telling you whether the object it is called on currently holds a meaningful value. Writing 'List() of String?' therefore means 'construct an empty List, then ask whether it is set' — not 'a List field that might be unset'.\n\nFIELD DECLARATION FORMS THAT WORK\n  names <- List() of String                 // empty List of String\n  scores <- Dict() of (String, Integer)     // empty Dict\n  tags <- [\"alpha\", \"beta\", \"gamma\"]         // List literal (type inferred)\n  counters as Integer: 0                    // explicit type + initial value\n  label as String: \"default\"                // explicit type + literal\n\nFIELD DECLARATION FORMS THAT FAIL WITH E04050\n  names <- List() of String?                // '?' invoked on the List\n  result <- compute() + 10                  // arithmetic\n  choice <- flag ? valueA : valueB          // no ternary in EK9 anyway\n\nWHEN IN DOUBT USE EXPLICIT TYPING\nIf the field's type is at all complex, abandon '<-' and use 'name as Type: value'. The explicit form tells the compiler the type up front and lets full expression processing happen later:\n\n  total as Float: price * quantity    // explicit type survives complex expr\n\nSee Q585 for field initialisation and constructor delegation. See Q104 for why EK9 requires explicit constructors on some uninitialised fields.","ek9Example":"defines module qa.classesandoop.e04050fieldfix\n\n  defines class\n\n    UniqueNames\n      names <- List() of String\n\n      add()\n        -> name as String\n        if not names contains name\n          names += name\n\n      count() as pure\n        <- rtn as Integer: length names\n\n      override operator ? as pure\n        <- rtn as Boolean: names?\n\n      operator $ as pure\n        <- rtn as String: `UniqueNames(${length names})`\n\n  defines program\n\n    E04050FieldFixDemo()\n      stdout <- Stdout()\n\n      names <- UniqueNames()\n      names.add(\"Alice\")\n      names.add(\"Bob\")\n      names.add(\"Alice\")\n      stdout.println($names)","migrationContext":"C#/Kotlin: 'List<string>?' means nullable List — a valid type. EK9: 'List() of String?' means 'call ? on the newly-created List' — produces Boolean, fails type inference. If you want a field that might be unset in EK9, the collection itself is always set (empty List IS valid); the question 'is this field populated' is answered by '?' on the field at the point you check it, not in the type declaration.","keywords":["E04050","List","complex expression","field declaration","is-set operator","nullable type","type inference"],"primaryTopics":["E04050 error","class field initialization","simple aggregate constructor"],"typicalErrors":[{"error":"E04050","correct":"      names <- List() of String","incorrect":"      names <- List() of String?","explanation":"The trailing '?' invokes the is-set operator on the constructed List, producing a Boolean. Type inference cannot reconcile that with a List-of-String field. Drop the '?' — EK9 has no nullable types, and the List itself is always set once created."}],"companions":[]}
{"id":1283,"category":"Classes and OOP","question":"When should I use 'name as Type: value' instead of 'name <- value' for a class field to avoid E04050?","url":"https://ek9.io/qa/QA1283.html","alternatePhrasings":["What's the difference between '<-' and 'as Type:' for class fields in EK9?","How do I initialize a class field with a complex expression without E04050?","When is explicit type annotation required in EK9 class field declarations?","Why does 'total <- price * quantity' fail in a class body?"],"answer":"EK9 offers two forms for initialising a class field and they are NOT interchangeable. Pick the right one for the expression you are writing and E04050 disappears.\n\nFORM 1 — '<-' INFERRED DECLARATION\nThe compiler infers the field type from a simple constructor call, a literal, or a list/dict literal. This runs at phase 2, BEFORE full expression processing.\n\n  count <- 0                                  // Integer\n  tags <- [\"alpha\", \"beta\"]                   // List of String\n  config <- {\"timeout\": 30, \"retries\": 3}     // Dict of (String, Integer)\n  items <- List() of String                   // empty List of String\n  scores <- Dict() of (String, Integer)       // empty Dict\n\nFORM 2 — 'as Type: value' EXPLICIT DECLARATION\nYou state the type up front, and the initialiser can be any expression. This tells the compiler the field's type without needing to resolve the initialiser first, so complex expressions are allowed.\n\n  totalPrice as Float: basePrice * quantityFactor\n  discount as Float: Float(0.0)\n  label as String: `Item ${itemId}`\n\nWHICH FORM TO USE\nIf the initialiser is a simple constructor call or a literal, use '<-'. If the initialiser is ANY of the following, use 'as Type:' instead:\n- Arithmetic or string interpolation involving other variables\n- A method call on an existing object\n- A function call that returns a value\n- A conditional or multi-branch expression\n\nTHE E04050 TRAP\nThe natural instinct 'just use <- everywhere' fails on expressions like:\n\n  total <- price * quantity                   // E04050 — arithmetic\n  label <- `Item ${itemId}`                   // E04050 — interpolation\n  head <- items.first()                       // E04050 — method call\n\nThe fix is mechanical: change '<-' to 'as <inferred-type>:' with no other edits.\n\n  total as Float: price * quantity            // OK\n  label as String: `Item ${itemId}`           // OK\n  head as String: items.first()               // OK\n\nSee Q585 for field initialisation rules and Q1282 for the specific is-set-operator trap.","ek9Example":"defines module qa.classesandoop.e04050explicittype\n\n  defines class\n\n    PriceTag\n      unitPrice as Float: Float()\n      itemId as String: String()\n\n      PriceTag()\n        ->\n          unitPrice as Float\n          itemId as String\n        this.unitPrice :=: unitPrice\n        this.itemId :=: itemId\n\n      label() as pure\n        <- rtn as String: `Item ${itemId} @ ${unitPrice}`\n\n      override operator ? as pure\n        <- rtn as Boolean: unitPrice? and itemId?\n\n      operator $ as pure\n        <- rtn as String: label()\n\n  defines program\n\n    E04050ExplicitTypeDemo()\n      stdout <- Stdout()\n\n      tag <- PriceTag(19.99, \"WIDGET-001\")\n      stdout.println($tag)","migrationContext":"Java/Kotlin/Swift all allow 'var total = price * quantity' in a class body — the inferrer runs after full expression resolution. EK9 field type inference runs earlier in the pipeline, so '<-' only works for simple constructors and literals. For anything else, EK9 asks you to state the type with 'as Type: value'. One character of annotation, complete clarity for the compiler.","keywords":["E04050","as Type","class field","explicit type","field initialisation","type inference"],"primaryTopics":["E04050 error","class field declaration forms","when explicit typing is required"],"typicalErrors":[{"error":"E04050","correct":"      unitPrice as Float: Float()","incorrect":"      unitPrice <- Float() + Float()","explanation":"Using '<-' with an arithmetic expression fails E04050 because type inference cannot resolve the expression type during phase 2. Use 'as Type: expression' instead — the explicit type annotation lets the compiler know the field's type without resolving the initialiser first."}],"companions":[]}
{"id":1286,"category":"Generics","question":"What does EK9 error E06060 mean on a generic class constructor and how do I fix it?","url":"https://ek9.io/qa/QA1286.html","alternatePhrasings":["I'm getting E06060 'generic type does not support private or protected constructors' — what's wrong?","Why can't I make my generic class constructor private in EK9?","How do I write a generic class constructor that compiles without E06060?","The compiler says my parameterised type constructor must be public — why?"],"answer":"E06060 fires when a generic type (a class declared as 'Name of type T' or 'Name of type (T, U)') has a PROTECTED constructor, or a PRIVATE PARAMETERISED constructor. The compiler reports this at phase 1 with a message like:\n\n  Error : E06060: 'Container' on line 14 position 22: a generic type does not support private or protected constructors\n\nThe rule is nuanced, not absolute: the PARAMETERISED (inferred) constructors must be public so the compiler can infer type arguments and instantiate from any context. The no-argument DEFAULT constructor may be 'default private'. Protected is never allowed.\n\nTHE FIX (private parameterised constructor)\nMake the parameterised constructor public — public is the default, so drop the access modifier:\n\n  Container() as pure\n    -> initialItem as T\n    item :=? initialItem\n\nGIVE THE OPTIONAL FIELD AN UNSET VALUE, NOT NULL\nA conceptual field 'item as T?' must not be left null by the no-arg constructor — give it an unset 'T()':\n\n  Container() as pure\n    item :=? T()\n\nTHE 'default private' ESCAPE HATCH IS ALLOWED ON GENERICS\nThe 'default private Container() as pure' idiom from regular classes DOES work on a generic — use it when the field cannot be given an unset 'T()' (T may be abstract, may hold a function, or may have no public no-arg constructor). External code then cannot create an empty Container; it must pass a value:\n\n  Container of type T\n    item as T?\n    default private Container() as pure       // allowed\n    Container() as pure\n      -> initialItem as T\n      item :=? initialItem\n\nWHY PARAMETERISED CONSTRUCTORS STAY PUBLIC\nGeneric types are reused with ANY valid type parameter; the compiler monomorphises each parameterisation (Container of String, Container of Integer, …). Type inference and instantiation need the parameterised constructor reachable from every call site, so it must be public. The no-arg default has no inference role, so it may be private.\n\nSee Q645 for the full rationale and the class-level rule comparison.","ek9Example":"defines module qa.genericsdeep.e06060fix\n\n  defines class\n\n    Container of type T\n      item as T?\n\n      Container() as pure\n        item :=? T()\n\n      Container() as pure\n        -> initialItem as T\n        item :=? initialItem\n\n      getItem() as pure\n        <- rtn as T: item\n\n      override operator ? as pure\n        <- rtn as Boolean: item?\n\n      operator $ as pure\n        <- rtn as String: $item\n\n  defines program\n\n    E06060FixDemo()\n      stdout <- Stdout()\n\n      stringContainer <- Container(\"hello\")\n      stdout.println($stringContainer)\n\n      integerContainer <- Container(42)\n      stdout.println($integerContainer)","migrationContext":"Java/C# generics allow private generic constructors freely (used with static factory methods). EK9 generic types require public constructors — if you want factory-style controlled construction, use a public constructor PLUS a public factory method that callers reach for by convention. The private-constructor-for-factory idiom does not cross into EK9 generic types.","keywords":["E06060","factory method","generic type","parameterised type","private constructor","protected constructor","public constructor"],"primaryTopics":["E06060 error","generic type constructors","constructor access on generics"],"typicalErrors":[{"error":"E06060","correct":"      Container() as pure\n        -> initialItem as T","incorrect":"      private Container() as pure\n        -> initialItem as T","explanation":"Marking a generic type's parameterised constructor as 'private' triggers E06060 - parameterised constructors on a generic type must be public so any parameterisation can instantiate. See ek9 -h E06060 for details."}],"companions":[]}
{"id":1287,"category":"Generics","question":"Can a generic class have multiple constructors in EK9, and do they all have to be public to avoid E06060?","url":"https://ek9.io/qa/QA1287.html","alternatePhrasings":["How do I write a generic class with more than one constructor in EK9?","Does E06060 block protected constructors as well as private ones on generic types?","Can I have an overloaded generic constructor where some are public and some are private?","Why does my generic class with a default constructor and a parameterised constructor fail E06060?"],"answer":"Generic classes in EK9 can have as many constructors as they need — overloading, default, parameterised. The access rule is nuanced, not absolute: the PARAMETERISED (inferred) constructors must be public so the compiler can infer type arguments and instantiate from any context; the no-argument DEFAULT constructor may additionally be 'default private' as an escape hatch; and PROTECTED is never allowed on any generic constructor.\n\n  Error : E06060: 'Box' on line 15 position 22: a generic type does not support private or protected constructors\n\nE06060 fires for a PROTECTED constructor (any), or for a PRIVATE PARAMETERISED constructor. It does NOT fire for a 'default private' no-arg constructor.\n\nCORRECT: MULTIPLE PUBLIC CONSTRUCTORS\n\n  defines class\n\n    Box of type T\n      item as T?\n      label as String?\n\n      Box() as pure\n        item :=? T()\n        label :=? String()\n\n      Box() as pure\n        -> initialItem as T\n        item :=? initialItem\n        label :=? String()\n\n      Box() as pure\n        ->\n          initialItem as T\n          tag as String\n        item :=? initialItem\n        label :=? tag\n\nThree constructors: no-argument, single-argument, two-argument. All public. All allowed. The no-arg constructor gives the conceptual field an unset 'T()' so it is present-but-unset, never null.\n\nCORRECT: PRIVATE NO-ARG DEFAULT (the escape hatch)\n\n  defines class\n\n    Box of type T\n      item as T?\n\n      default private Box() as pure           // allowed: the no-arg default may be private\n\n      Box() as pure\n        -> initialItem as T\n        item :=? initialItem\n\nWhen the field cannot be given an unset 'T()' (T may be abstract, may hold a function, or may have no public no-arg constructor), make the no-arg default private. External code then cannot create an empty Box; it must pass a value to the inferred constructor.\n\nINCORRECT: PRIVATE OR PROTECTED PARAMETERISED CONSTRUCTOR\n\n  defines class\n\n    Box of type T\n      item as T?\n\n      Box() as pure\n        item :=? T()\n\n      private Box() as pure                    // E06060 — a parameterised constructor must be public\n        -> initialItem as T\n        item :=? initialItem\n\nType inference needs the parameterised constructor public, so making it private (or protected) is E06060.\n\nHIDING VALIDATED CONSTRUCTION: A FACTORY FUNCTION\nTo route callers through validation, keep the public parameterised constructors and add a factory function in the same module:\n\n  defines function\n\n    createValidatedBox() as pure\n      -> value as String\n      <- rtn as Box of String?\n      if value?\n        rtn: Box(value)\n\nUse 'default private' to forbid EMPTY construction; use a factory to control VALUED construction with validation, defaulting, or logging.\n\nSee Q1286 for the single-constructor form. See Q645 for the private no-arg escape hatch.","ek9Example":"defines module qa.genericsdeep.e06060multi\n\n  defines class\n\n    Box of type (T, U)\n      first as T?\n      second as U?\n\n      Box() as pure\n        first :=? T()\n        second :=? U()\n\n      Box() as pure\n        ->\n          firstValue as T\n          secondValue as U\n        first :=? firstValue\n        second :=? secondValue\n\n      getFirst() as pure\n        <- rtn as T: first\n\n      getSecond() as pure\n        <- rtn as U: second\n\n      override operator ? as pure\n        <- rtn as Boolean: first? and second?\n\n      operator $ as pure\n        <- rtn as String: `(${first}, ${second})`\n\n  defines program\n\n    E06060MultiDemo()\n      stdout <- Stdout()\n\n      labelled <- Box(42, \"answer\")\n      stdout.println($labelled)\n\n      keyed <- Box(\"alpha\", 3.14)\n      stdout.println($keyed)","migrationContext":"C#/Java allow private constructors on generic types, commonly paired with static factory methods inside the same class for controlled construction. EK9 takes a different position: generic constructors are public by rule, and factory functions live alongside the generic in the same module. This keeps monomorphisation simple and the code-generation stage deterministic.","keywords":["E06060","constructor overloading","generic type","monomorphisation","multiple constructors","public constructor"],"primaryTopics":["E06060 error","generic constructor rules","multiple constructor overloads"],"typicalErrors":[{"error":"E06060","correct":"      Box() as pure\n        first :=? T()","incorrect":"      protected Box() as pure\n        first :=? T()","explanation":"Generic types reject both 'private' and 'protected' constructor modifiers absolutely; a 'protected' constructor on a generic type fires E06060. See ek9 -h E06060 for details."}],"companions":[]}
{"id":1290,"category":"Dependency Injection","question":"What does EK9 error E50040 mean when I write an abstract method in a component and how do I fix it?","url":"https://ek9.io/qa/QA1290.html","alternatePhrasings":["I'm getting E50040 'cannot be abstract' on a method inside defines component — what's wrong?","Why can't I write 'log() as abstract' inside my EK9 Logger component?","How do I make an extensible Logger component in EK9 without E50040?","The compiler says my abstract method's container is not abstract — how do I fix it?"],"answer":"E50040 fires when a method is declared 'as abstract' inside a construct that has not itself been marked 'as abstract'. For components the error usually reads:\n\n  Error : E50040: 'log' on line 7 position 6: is abstract, but construct on line 6 is not marked as abstract, 'public Void <- log(message as String) as abstract': cannot be abstract\n\nThe rule is symmetric: a method can only be 'as abstract' if the enclosing class, trait or component is ALSO 'as abstract'. Otherwise the compiler rejects the declaration at phase 1.\n\nTHE TWO VALID FIXES\n\n1. Mark the enclosing component as abstract (preferred when you want an extensible base component that subclasses override):\n\n  defines component\n\n    Logger as abstract\n      log() as abstract\n        -> message as String\n\n    LoggerImpl extends Logger\n      override log()\n        -> message as String\n        stdout <- Stdout()\n        stdout.println(`LOG: ${message}`)\n\n2. Give the method a concrete implementation and drop 'as abstract' (preferred when the base component already has a sensible default that subclasses can override):\n\n  defines component\n\n    Logger\n      log()\n        -> message as String\n        // default implementation\n        stdout <- Stdout()\n        stdout.println(`LOG: ${message}`)\n\nCOMPONENTS IN EK9\nComponents are the dependency-injection unit: they are registered in an application with 'register ComponentType() as Interface' and injected into other components and programs via 'field as Type!'. A component can be abstract (base type for a hierarchy) or concrete (a directly-instantiable implementation). The standard pattern is: ONE abstract component with the interface methods, ONE (or more) concrete extending components with the actual implementations, and the 'register' clause nominates the concrete implementation for the abstract interface.\n\nWHY NOT USE A TRAIT?\nTraits are for mix-in behaviour across class hierarchies — they compose interfaces without being the DI unit themselves. Components are specifically for dependency injection. Use a trait when you want multiple classes to share a method surface; use an abstract component when you want an injectable interface with multiple implementations registered per environment (test vs production, for example).\n\nSee Q103 for abstract classes and methods. See Q111 for components. See Q677 for how abstract flows through three-level hierarchies.","ek9Example":"defines module qa.dependencyinjection.e50040fix\n\n  defines component\n\n    Logger as abstract\n      log() as abstract\n        -> message as String\n\n    LoggerImpl extends Logger\n      override log()\n        -> message as String\n        stdout <- Stdout()\n        stdout.println(`LOG: ${message}`)\n\n    UserService as abstract\n      createUser() as abstract\n        -> userName as String\n\n    UserServiceImpl extends UserService\n      logger as Logger!\n\n      override createUser()\n        -> userName as String\n        logger.log(`Created user: ${userName}`)\n\n      override operator ? as pure\n        <- rtn as Boolean: logger?\n\n  defines application\n\n    MyApp\n      register LoggerImpl() as Logger\n      register UserServiceImpl() as UserService\n\n  defines program\n\n    E50040FixDemo() with application of MyApp\n      service as UserService!\n      service.createUser(\"Alice\")\n      service.createUser(\"Bob\")","migrationContext":"Java interfaces with abstract methods: the interface itself is implicitly abstract. EK9 components: you must state 'as abstract' explicitly on the containing component if the component has abstract methods. There is no implicit abstractness — 'defines component \\n Logger \\n log() as abstract' is a specific error (E50040), not a lenient shortcut.","keywords":["E50040","Logger","abstract component","abstract method","component","dependency injection"],"primaryTopics":["E50040 error","abstract modifier on components","component inheritance"],"typicalErrors":[{"error":"E50040","correct":"    Logger as abstract","incorrect":"    Logger","explanation":"Declaring a method 'as abstract' inside 'defines component / Logger' without marking Logger itself 'as abstract' fires E50040. A method can only be abstract when its enclosing construct is also abstract. Add 'as abstract' to the component header to fix it, or drop 'as abstract' from the method and provide a concrete implementation."}],"companions":[]}
{"id":1291,"category":"Dependency Injection","question":"Why does E50040 fire on my abstract method in a class but not in a trait?","url":"https://ek9.io/qa/QA1291.html","alternatePhrasings":["When do I need to mark a construct 'as abstract' in EK9 — is it only classes and components?","Why does my trait compile with an abstract method but my class doesn't?","What's the difference between trait abstract methods and class abstract methods in EK9?","How do traits avoid the E50040 rule that classes and components are subject to?"],"answer":"E50040 fires when an abstract method appears inside a construct that the compiler does not accept as an abstract container. The rule is NOT uniform across all constructs — it matters which kind of construct you are writing.\n\nWHERE 'as abstract' IS REQUIRED ON THE CONTAINING CONSTRUCT\n\n- Classes: a class with an abstract method must be declared 'Shape as abstract'\n- Components: a component with an abstract method must be declared 'Logger as abstract'\n\nWhy: classes and components are instantiable by default. The compiler assumes you mean a concrete type unless you opt into abstractness. Leaving an abstract method inside a concrete container would produce an incomplete type with no way to construct it safely.\n\nWHERE 'as abstract' IS NOT REQUIRED\n\n- Traits: a trait is an inherently abstract interface-like construct. Abstract methods are the default expectation. You simply declare 'describe() as abstract' inside a trait and it compiles.\n- Generic function declarations that happen to be abstract (E50040 is not the error code for those — a different error applies).\n\nCOMPARE THE TWO CASES DIRECTLY\n\n  defines trait\n\n    Printable                              // no 'as abstract' needed\n      describe() as pure abstract          // OK — trait accepts abstract methods\n        <- rtn as String?\n\n  defines class\n\n    Shape                                  // E50040 on area() below!\n      area() as abstract                   // because 'Shape' is not 'as abstract'\n        <- rtn as Float?\n\nThe trait compiles. The class does not — it reports E50040 on 'area'. Fixing the class means EITHER marking the container 'Shape as abstract' OR dropping 'as abstract' from the method and giving it a default implementation.\n\nDI IMPLICATIONS\nFor dependency injection hierarchies, the idiomatic EK9 shape is:\n\n- Declare the interface as a TRAIT (no 'as abstract' needed; all methods abstract by default)\n- Declare an ABSTRACT CLASS or ABSTRACT COMPONENT that implements the trait and factors out shared state\n- Declare CONCRETE subclasses or subcomponents that override the remaining abstract methods\n- Register the concrete type in the application\n\nThis lets you express 'some shared implementation + some pluggable methods' without fighting E50040. If you find yourself reaching for 'as abstract' on a class or component, ask first whether the abstraction really belongs on a trait — traits are the lighter-weight vehicle for this kind of contract.\n\nSee Q1290 for the component-with-abstract-method variant. See Q103 for the full classes/methods/abstract story.","ek9Example":"defines module qa.dependencyinjection.e50040classvstrait\n\n  defines trait\n\n    Printable\n      describe() as pure abstract\n        <- rtn as String?\n\n  defines class\n\n    Shape as abstract\n      area() as pure abstract\n        <- rtn as Float?\n\n    Circle extends Shape with trait of Printable\n      radius as Float: Float()\n\n      Circle()\n        -> radius as Float\n        this.radius :=: radius\n\n      override area() as pure\n        <- rtn as Float: 3.14159 * radius * radius\n\n      override describe() as pure\n        <- rtn as String: `Circle(r=${radius})`\n\n      override operator ? as pure\n        <- rtn as Boolean: radius?\n\n  defines program\n\n    E50040ClassVsTraitDemo()\n      stdout <- Stdout()\n      c <- Circle(5.0)\n      stdout.println(c.describe())\n      stdout.println(`area=${c.area()}`)","migrationContext":"Java: every interface method is implicitly abstract; abstract classes require the 'abstract' keyword on the class for abstract methods. EK9: traits behave like Java interfaces (methods implicitly abstract, no container modifier needed); classes and components behave more strictly than Java abstract classes (the container MUST be marked 'as abstract' if it contains any abstract methods). The distinction surfaces in E50040 — it fires on classes and components, never on traits.","keywords":["E50040","abstract class","abstract method","component","dependency injection","trait"],"primaryTopics":["E50040 error","abstract modifier rules","trait vs class abstractness"],"typicalErrors":[{"error":"E50040","correct":"    Shape as abstract","incorrect":"    Shape","explanation":"Declaring 'area() as abstract' inside 'defines class / Shape' without marking Shape itself 'as abstract' fires E50040. Unlike traits, classes are concrete by default and must opt into abstractness. Add 'as abstract' to the class header — 'Shape as abstract' — or convert the declaration to a trait if you only need an interface-like contract."}],"companions":[]}
{"id":1292,"category":"Web Services","question":"How does service security posture work with 'open' and 'constrain by'?","url":"https://ek9.io/qa/QA1292.html","alternatePhrasings":["What is E12050 service security not declared?","How do I declare security on an EK9 service?","What is the difference between open and constrain by on a service?","Can service operations have their own security posture?"],"answer":"Every EK9 service operation must have a security posture. The posture is either inherited from the parent service or declared on the operation itself.\n\nSERVICE-LEVEL POSTURE\nDeclare 'open' or 'constrain by X' on the service. All operations inherit:\n  PublicApi :/api open\n  SecureApi :/api constrain by JwtGate\n\nOPERATION-LEVEL POSTURE\nWhen the service has no posture, each operation must declare its own:\n  MixedApi :/api\n    health() as GET for :/status open\n    data() as GET for :/data constrain by JwtGate\n\nWHY REQUIRED (E12050)\nSecurity must be a conscious decision. A service that silently defaults to 'open' creates bugs where developers forget authentication. The compiler enforces explicit posture on every endpoint.\n\nSECURITYGATE AND CORSPOLICY\nConstraints reference functions extending SecurityGate or CORSPolicy:\n  MyGate is SecurityGate — authenticates requests\n  MyCors is CORSPolicy — validates CORS origins\nUse 'constrain by MyGate' for auth only, or 'constrain by MyGate and MyCors' for auth plus CORS.\n\nSee Q1293 for constraint type resolution. See Q1294 for constraint type validation. See Q1295 for duplicate constraint roles. See Q657 for URI mapping. See Q685 for method bodies.","ek9Example":"defines module qa.webdeep.securityposture\n\n  defines constant\n\n    jsonType <- \"application/json\"\n    noCache <- \"no-cache\"\n    langEn <- \"en\"\n    okStatus <- 200\n\n  defines function\n\n    <?-\n      SecurityGate implementation that accepts all authenticated requests.\n    -?>\n    JwtGate is SecurityGate\n      -> context as HTTPContext\n      <- rtn as HTTPContext: context\n\n    <?-\n      CORSPolicy that allows specific origins.\n    -?>\n    AllowedOrigins is CORSPolicy\n      -> origin as String\n      <- rtn as Boolean: true\n\n  defines service\n\n    <?-\n      Service-level posture: all operations inherit 'open'.\n    -?>\n    PublicApi :/api/public open\n\n      health() as GET for :/health\n        <- response as HTTPResponse: () with trait HTTPResponse\n          override content()\n            <- rtn as String: `{\"status\": \"healthy\"}`\n          override status() as pure\n            <- rtn as Integer: okStatus\n          override contentType() as pure\n            <- rtn as String: jsonType\n          override cacheControl() as pure\n            <- rtn as String: noCache\n          override contentLanguage() as pure\n            <- rtn as String: langEn\n          default operator ?\n\n    <?-\n      Service-level posture: all operations inherit 'constrain by'.\n    -?>\n    SecureApi :/api/secure constrain by JwtGate\n\n      data() as GET for :/data\n        <- response as HTTPResponse: () with trait HTTPResponse\n          override content()\n            <- rtn as String: `{\"items\": []}`\n          override status() as pure\n            <- rtn as Integer: okStatus\n          override contentType() as pure\n            <- rtn as String: jsonType\n          override cacheControl() as pure\n            <- rtn as String: noCache\n          override contentLanguage() as pure\n            <- rtn as String: langEn\n          default operator ?\n\n    <?-\n      Service with both SecurityGate and CORSPolicy.\n    -?>\n    FullApi :/api/full constrain by JwtGate and AllowedOrigins\n\n      info() as GET for :/info\n        <- response as HTTPResponse: () with trait HTTPResponse\n          override content()\n            <- rtn as String: `{\"name\": \"FullApi\"}`\n          override status() as pure\n            <- rtn as Integer: okStatus\n          override contentType() as pure\n            <- rtn as String: jsonType\n          override cacheControl() as pure\n            <- rtn as String: noCache\n          override contentLanguage() as pure\n            <- rtn as String: langEn\n          default operator ?\n\n    <?-\n      Operation-level posture: service has none, each operation declares its own.\n    -?>\n    MixedApi :/api/mixed\n\n      health() as GET for :/health open\n        <- response as HTTPResponse: () with trait HTTPResponse\n          override content()\n            <- rtn as String: \"ok\"\n          override status() as pure\n            <- rtn as Integer: okStatus\n          override contentType() as pure\n            <- rtn as String: \"text/plain\"\n          override cacheControl() as pure\n            <- rtn as String: noCache\n          override contentLanguage() as pure\n            <- rtn as String: langEn\n          default operator ?\n\n      secure() as GET for :/secure constrain by JwtGate\n        <- response as HTTPResponse: () with trait HTTPResponse\n          override content()\n            <- rtn as String: `{\"secure\": true}`\n          override status() as pure\n            <- rtn as Integer: okStatus\n          override contentType() as pure\n            <- rtn as String: jsonType\n          override cacheControl() as pure\n            <- rtn as String: noCache\n          override contentLanguage() as pure\n            <- rtn as String: langEn\n          default operator ?\n\n  defines application\n\n    SecurityPostureApp\n      register PublicApi()\n      register SecureApi()\n      register FullApi()\n      register MixedApi()\n\n  defines program\n\n    ServiceSecurityPostureDemo()\n      stdout <- Stdout()\n      stdout.println(\"Service security posture patterns:\")\n      stdout.println(\"  PublicApi: service-level 'open'\")\n      stdout.println(\"  SecureApi: service-level 'constrain by JwtGate'\")\n      stdout.println(\"  FullApi: service-level 'constrain by JwtGate and AllowedOrigins'\")\n      stdout.println(\"  MixedApi: operation-level mixed postures\")","migrationContext":"Java: Spring Security uses @PreAuthorize or SecurityFilterChain — separate from controllers. Python: Flask-Login or decorators, optional and easily forgotten. Go: middleware wrapping, no compile-time check. Rust: Actix middleware, runtime only. EK9: security posture is part of the service grammar, enforced at compile time. No endpoint can exist without an explicit posture.","keywords":["CORS","CORSPolicy","E12050","SecurityGate","authentication","constrain","http","open","posture","security","service"],"primaryTopics":[],"typicalErrors":[{"error":"E12050","correct":"    PublicApi :/api/public open","incorrect":"    PublicApi :/api/public","explanation":"A service with no security posture whose operation also lacks its own posture is rejected — add 'open' or 'constrain by X'. See ek9 -h E12050 for details."}],"companions":[]}
{"id":1293,"category":"Web Services","question":"What happens when a constrain by reference cannot be resolved?","url":"https://ek9.io/qa/QA1293.html","alternatePhrasings":["What is E12060 constraint type not resolved?","Why does the compiler reject my constrain by clause?","How do I fix an unresolved security constraint reference?","What must I import for constrain by to work?"],"answer":"When a 'constrain by' clause names a type that does not exist, the compiler emits E12060.\n\nWHAT CAUSES E12060\nThe constraint reference name cannot be found in the current module scope or imports:\n  MyApi :/api constrain by NonExistentGate\nThe compiler searches for 'NonExistentGate' but finds nothing.\n\nCOMMON CAUSES\n1. Spelling error in the constraint name\n2. Missing import for the module containing the function\n3. The function has not been defined yet\n4. The function is in a different module without a 'references' declaration\n\nHOW TO FIX\nDefine a function that extends SecurityGate or CORSPolicy in the same module, or import it:\n  defines function\n    JwtGate is SecurityGate\n      -> context as HTTPContext\n      <- rtn as HTTPContext: context\n\nThen reference it:\n  MyApi :/api constrain by JwtGate\n\nSee Q1292 for security posture overview. See Q1294 for constraint type validation. See Q1295 for duplicate constraint roles.","ek9Example":"defines module qa.webdeep.constraintresolution\n\n  defines constant\n\n    jsonType <- \"application/json\"\n    noCache <- \"no-cache\"\n    langEn <- \"en\"\n    okStatus <- 200\n\n  defines function\n\n    <?-\n      SecurityGate implementation for JWT authentication.\n    -?>\n    JwtGate is SecurityGate\n      -> context as HTTPContext\n      <- rtn as HTTPContext: context\n\n  defines service\n\n    <?-\n      Service with a valid, resolvable constraint reference.\n      JwtGate is defined in this module so it resolves correctly.\n    -?>\n    ProtectedApi :/api/protected constrain by JwtGate\n\n      resource() as GET for :/resource\n        <- response as HTTPResponse: () with trait HTTPResponse\n          override content()\n            <- rtn as String: `{\"protected\": true}`\n          override status() as pure\n            <- rtn as Integer: okStatus\n          override contentType() as pure\n            <- rtn as String: jsonType\n          override cacheControl() as pure\n            <- rtn as String: noCache\n          override contentLanguage() as pure\n            <- rtn as String: langEn\n          default operator ?\n\n  defines application\n\n    ConstraintResolutionApp\n      register ProtectedApi()\n\n  defines program\n\n    ConstraintResolutionDemo()\n      stdout <- Stdout()\n      stdout.println(\"Constraint type resolution:\")\n      stdout.println(\"  JwtGate defined in same module -> resolves OK\")\n      stdout.println(\"  'constrain by JwtGate' compiles successfully\")\n      stdout.println(\"  Missing or misspelled names trigger E12060\")","migrationContext":"Java: Spring Security bean references are resolved at runtime — a typo causes a NoSuchBeanException at startup. Python: decorator references fail at import time. Go: middleware references are compile-time checked. Rust: type references are compile-time checked. EK9: constraint references are resolved at compile time (Phase 5), so typos and missing imports are caught before deployment.","keywords":["CORSPolicy","E12060","SecurityGate","constrain","http","import","reference","resolve","service"],"primaryTopics":[],"typicalErrors":[{"error":"E12060","correct":":/api/protected constrain by JwtGate","incorrect":":/api/protected constrain by NonExistentGate","explanation":"The 'constrain by' reference must resolve to an existing function; NonExistentGate is undefined, so the constraint type cannot be resolved. See ek9 -h E12060 for details."}],"companions":[]}
{"id":1294,"category":"Web Services","question":"What types are valid in a constrain by clause?","url":"https://ek9.io/qa/QA1294.html","alternatePhrasings":["What is E12062 constraint type invalid?","Why must constrain by reference SecurityGate or CORSPolicy?","Can I use any function in a constrain by clause?","What types does the compiler accept for security constraints?"],"answer":"A 'constrain by' clause must reference functions that extend SecurityGate, CORSPolicy, or both. The compiler emits E12062 if the referenced type does not extend either.\n\nVALID CONSTRAINT TYPES\nSecurityGate: authenticates HTTP requests.\n  MyGate is SecurityGate\n    -> context as HTTPContext\n    <- rtn as HTTPContext: context\n\nCORSPolicy: validates cross-origin requests.\n  MyCors is CORSPolicy\n    -> origin as String\n    <- rtn as Boolean: true\n\nINVALID CONSTRAINT TYPES (E12062)\nA regular function or class that does not extend either base:\n  defines function\n    NotAGate()\n      <- rtn as String: \"hello\"\n  defines service\n    MyApi :/api constrain by NotAGate  <!- E12062 -!>\n\nWHY THIS RESTRICTION\nThe router needs to know what to do with the constraint at runtime. SecurityGate functions receive HTTPContext and return an enriched context (or unset for rejection). CORSPolicy functions receive an origin String and return Boolean. Arbitrary functions have unknown signatures.\n\nSINGLE VS DUAL CONSTRAINTS\nOne constraint: 'constrain by MyGate' (SecurityGate only)\nTwo constraints: 'constrain by MyGate and MyCors' (one SecurityGate + one CORSPolicy)\n\nSee Q1292 for security posture overview. See Q1293 for constraint resolution. See Q1295 for duplicate constraint roles.","ek9Example":"defines module qa.webdeep.constraintvalidation\n\n  defines constant\n\n    jsonType <- \"application/json\"\n    noCache <- \"no-cache\"\n    langEn <- \"en\"\n    okStatus <- 200\n\n  defines function\n\n    <?-\n      Valid SecurityGate: receives HTTPContext, returns HTTPContext.\n    -?>\n    TokenGate is SecurityGate\n      -> context as HTTPContext\n      <- rtn as HTTPContext: context\n\n    <?-\n      Valid CORSPolicy: receives origin String, returns Boolean.\n    -?>\n    OriginPolicy is CORSPolicy\n      -> origin as String\n      <- rtn as Boolean: true\n\n  defines service\n\n    <?-\n      Service using a valid SecurityGate constraint.\n    -?>\n    GatedService :/api/gated constrain by TokenGate\n\n      items() as GET for :/items\n        <- response as HTTPResponse: () with trait HTTPResponse\n          override content()\n            <- rtn as String: `{\"items\": [\"a\", \"b\"]}`\n          override status() as pure\n            <- rtn as Integer: okStatus\n          override contentType() as pure\n            <- rtn as String: jsonType\n          override cacheControl() as pure\n            <- rtn as String: noCache\n          override contentLanguage() as pure\n            <- rtn as String: langEn\n          default operator ?\n\n    <?-\n      Service using both SecurityGate and CORSPolicy.\n    -?>\n    GatedWithCors :/api/cors constrain by TokenGate and OriginPolicy\n\n      items() as GET for :/items\n        <- response as HTTPResponse: () with trait HTTPResponse\n          override content()\n            <- rtn as String: `{\"items\": [\"c\", \"d\"]}`\n          override status() as pure\n            <- rtn as Integer: okStatus\n          override contentType() as pure\n            <- rtn as String: jsonType\n          override cacheControl() as pure\n            <- rtn as String: noCache\n          override contentLanguage() as pure\n            <- rtn as String: langEn\n          default operator ?\n\n  defines application\n\n    ConstraintValidationApp\n      register GatedService()\n      register GatedWithCors()\n\n  defines program\n\n    ConstraintTypeValidationDemo()\n      stdout <- Stdout()\n      stdout.println(\"Constraint type validation:\")\n      stdout.println(\"  TokenGate is SecurityGate -> valid\")\n      stdout.println(\"  OriginPolicy is CORSPolicy -> valid\")\n      stdout.println(\"  Regular functions -> E12062\")","migrationContext":"Java: Spring Security filters can be any class implementing OncePerRequestFilter — no compile-time type check. Python: Flask decorators are unchecked functions. Go: middleware is any http.Handler wrapper. Rust: Actix middleware must implement Transform trait. EK9: constraints must extend SecurityGate or CORSPolicy, verified at compile time.","keywords":["CORSPolicy","E12062","SecurityGate","constrain","function","http","service","type","validation"],"primaryTopics":[],"typicalErrors":[{"error":"E12062","correct":"TokenGate is SecurityGate\n      -> context as HTTPContext","incorrect":"TokenGate()\n      -> context as HTTPContext","explanation":"A 'constrain by' reference must extend SecurityGate or CORSPolicy; a plain function does not qualify. See ek9 -h E12062 for details."}],"companions":[]}
{"id":1295,"category":"Web Services","question":"Why does constrain by X and Y fail when both extend SecurityGate?","url":"https://ek9.io/qa/QA1295.html","alternatePhrasings":["What is E12064 duplicate constraint role?","Can I use two SecurityGate functions in constrain by?","Why must the two constraint references have different roles?","How do I combine SecurityGate and CORSPolicy in one clause?"],"answer":"When 'constrain by X and Y' names two functions that both extend the same base (both SecurityGate or both CORSPolicy), the compiler emits E12064.\n\nWHAT CAUSES E12064\nDuplicate roles in a two-reference constraint:\n  constrain by GateAlpha and GateBeta\nIf both GateAlpha and GateBeta extend SecurityGate, the roles are duplicated.\n\nWHY ONE OF EACH\nThe runtime applies the two constraints differently:\n  SecurityGate: called with HTTPContext, authenticates the request\n  CORSPolicy: called with origin String, validates cross-origin access\nTwo gates or two policies create ambiguity — which one runs first? What happens if they disagree?\n\nCORRECT PATTERN\nOne SecurityGate + one CORSPolicy:\n  constrain by JwtGate and AllowedOrigins\nThe router knows exactly which function handles authentication and which handles CORS.\n\nSINGLE CONSTRAINT IS FINE\nIf you only need authentication without CORS:\n  constrain by JwtGate\nOr only CORS without authentication (unusual):\n  constrain by MyCors\n\nSee Q1292 for security posture overview. See Q1293 for constraint resolution. See Q1294 for constraint type validation.","ek9Example":"defines module qa.webdeep.duplicaterole\n\n  defines constant\n\n    jsonType <- \"application/json\"\n    noCache <- \"no-cache\"\n    langEn <- \"en\"\n    okStatus <- 200\n\n  defines function\n\n    <?-\n      SecurityGate for JWT token validation.\n    -?>\n    JwtGate is SecurityGate\n      -> context as HTTPContext\n      <- rtn as HTTPContext: context\n\n    <?-\n      CORSPolicy that allows known origins.\n    -?>\n    KnownOrigins is CORSPolicy\n      -> origin as String\n      <- rtn as Boolean: true\n\n  defines service\n\n    <?-\n      Correct: one SecurityGate + one CORSPolicy.\n      The router knows which function handles which concern.\n    -?>\n    ValidDualConstraint :/api/dual constrain by JwtGate and KnownOrigins\n\n      resource() as GET for :/resource\n        <- response as HTTPResponse: () with trait HTTPResponse\n          override content()\n            <- rtn as String: `{\"dual\": \"gate+cors\"}`\n          override status() as pure\n            <- rtn as Integer: okStatus\n          override contentType() as pure\n            <- rtn as String: jsonType\n          override cacheControl() as pure\n            <- rtn as String: noCache\n          override contentLanguage() as pure\n            <- rtn as String: langEn\n          default operator ?\n\n  defines application\n\n    DuplicateRoleApp\n      register ValidDualConstraint()\n\n  defines program\n\n    DuplicateConstraintRoleDemo()\n      stdout <- Stdout()\n      stdout.println(\"Duplicate constraint role rules:\")\n      stdout.println(\"  'constrain by Gate and Cors' -> valid (different roles)\")\n      stdout.println(\"  'constrain by Gate1 and Gate2' -> E12064 (both SecurityGate)\")\n      stdout.println(\"  'constrain by Cors1 and Cors2' -> E12064 (both CORSPolicy)\")","migrationContext":"Java: Spring Security allows chaining multiple filters of the same type — order is explicit in filter chain. Python: multiple decorators stack. Go: middleware wraps in order. Rust: Actix Transform chain. EK9: at most one SecurityGate and one CORSPolicy per constraint clause, enforced at compile time. No ambiguous filter ordering.","keywords":["CORSPolicy","E12064","SecurityGate","constrain","duplicate","http","role","service"],"primaryTopics":[],"typicalErrors":[],"companions":[]}
{"id":1296,"category":"Advanced Type System","question":"How do I capture a value into a dynamic function/class without sharing mutations with the caller?","url":"https://ek9.io/qa/QA1296.html","alternatePhrasings":["Are dynamic captures by value or by reference in EK9?","If I capture a List into a closure and the closure mutates it, does the caller see the change?","How do I take a snapshot of a value when capturing into a dynamic class?","What is the EK9 equivalent of Java's effectively-final or JavaScript's closure-by-reference?"],"answer":"EK9 captures hold the POINTER value at capture time, not a deep copy. Each dynamic class/function instance has its own pointer slot — outer pointer rebinding (`outer := newValue`) does NOT affect the closure. But mutating the captured object via `+=`, `:=:`, `:~:`, or any other operator that modifies the pointed-to object IS visible to the caller, because both share the same object.\n\nTHREE PATTERNS:\n\nPattern 1 — Default capture (shared mutation):\n  original <- \"Hello\"\n  sharedAppender <- (original) with trait of Appendable as class\n    override append()\n      -> extra as String\n      original += extra   //mutates the SHARED String\n    default operator ?\n  sharedAppender.append(\" World\")\n  //outer 'original' is now \"Hello World\"\n\nPattern 2 — Isolation via `:=:` copy before capture:\n  master <- \"Master\"\n  snapshot <- String()\n  snapshot :=: master   //copy contents into snapshot\n  isolatedAppender <- (snapshot) with trait of Appendable as class\n    ...mutates snapshot, not master\n\nPattern 3 — Isolation via copy constructor:\n  base <- \"Base\"\n  copied <- String(base)   //String's copy constructor\n  copyAppender <- (copied) with trait of Appendable as class\n    ...mutates copied, not base\n\nIMPORTANT — `:=:` IS NOT ALWAYS DEEP\nThe `default operator :=:` on records does a SHALLOW copy (copies field references). For deep copy of nested mutable state, the type must implement its own `:=:` that recursively calls `:=:` on each field. See operators.html for the deep-vs-shallow distinction. The same caveat applies to copy constructors — whether they deep-copy or share references is up to the constructor's implementation.\n\nThis is the same issue Java has with reference types — EK9's advantage is that the operators (`:=` rebind vs `:=:` copy vs `:~:` merge) make the intent grammatically explicit instead of per-API guessing.\n\nSee Q1125 for dynamic function basics. See Q1126 for dynamic class basics. See Q1128 for dynamic class as argument.","ek9Example":"defines module qa.advancedtypes.dynamiccaptureisolation\n\n  defines trait\n\n    Appendable\n      append() as abstract\n        -> extra as String\n\n      override operator ? as pure\n        <- rtn as Boolean: true\n\n  defines program\n\n    DynamicCaptureIsolationDemo()\n      stdout <- Stdout()\n\n      //--- Pattern 1: Default capture — mutation is SHARED with caller ---\n      //Captures hold pointer values; mutating the captured object affects the outer scope\n      original <- \"Hello\"\n      sharedAppender <- (original) with trait of Appendable as class\n        override append()\n          -> extra as String\n          original += extra\n        default operator ?\n\n      stdout.println(original)\n      sharedAppender.append(\" World\")\n      stdout.println(original)\n\n      //--- Pattern 2: Snapshot via :=: copy operator BEFORE capture ---\n      //Take a copy first, then capture the copy — outer is isolated\n      master <- \"Master\"\n      snapshot <- String()\n      snapshot :=: master\n      isolatedAppender <- (snapshot) with trait of Appendable as class\n        override append()\n          -> extra as String\n          snapshot += extra\n        default operator ?\n\n      isolatedAppender.append(\" Backup\")\n      stdout.println(master)\n      stdout.println(snapshot)\n\n      //--- Pattern 3: Snapshot via copy constructor ---\n      //Construct a new instance from the original — same isolation, different idiom\n      base <- \"Base\"\n      copied <- String(base)\n      copyAppender <- (copied) with trait of Appendable as class\n        override append()\n          -> extra as String\n          copied += extra\n        default operator ?\n\n      copyAppender.append(\" Edition\")\n      stdout.println(base)\n      stdout.println(copied)","migrationContext":"Java: lambda captures are effectively final — outer rebind impossible at the language level, but the captured object is shared by reference and mutating its state IS visible to the caller (e.g., captured List). Same issue, less explicit. JavaScript: closures capture by scope-chain reference for both rebind AND mutation — even more permissive than EK9. Rust: closures distinguish Fn (read-only borrow), FnMut (mutable borrow), FnOnce (move) at the type-system level — explicit ownership semantics make isolation vs sharing a compile-time property. Kotlin: lambdas capture by reference (val and var both); mutation of captured mutable objects is visible. EK9: capture is by-pointer; rebind (`:=`) inside closure is local-only, mutation operators are shared with caller. Use `:=:` or copy constructor for explicit snapshot.","keywords":[":=:","by reference","by value","capture","closure","copy constructor","deep copy","dynamic","isolation","mutation","shallow copy","snapshot"],"primaryTopics":["capture semantics","closure isolation","deep vs shallow copy"],"typicalErrors":[{"error":"design","correct":"      snapshot <- String()\n      snapshot :=: master\n      isolatedAppender <- (snapshot) with trait of Appendable as class\n        override append()\n          -> extra as String\n          snapshot += extra\n        default operator ?","incorrect":"isolated <- (master) with trait of Appendable as class\n        override append()\n          -> extra as String\n          master += extra   //BUG: mutates the caller's master\n        default operator ?","explanation":"Capturing a mutable value directly does NOT isolate the closure from the caller. Mutating operators (+=, :=:, :~:) on a captured object affect the SAME object the caller sees. To isolate, copy the value via :=: or a copy constructor BEFORE capture."}],"companions":[]}
{"id":1297,"category":"Classes and OOP","question":"Can records extend other records in EK9?","url":"https://ek9.io/qa/QA1297.html","alternatePhrasings":["How do I inherit one record from another in EK9?","Does EK9 support record inheritance?","How does 'as open' apply to records?","How do I override operators on a child record and call the parent's operator?","What does super.<=>(other) mean on a record operator?"],"answer":"Yes. Records in EK9 follow the same closed-by-default + 'as open' opt-in rule as classes. Mark a base record 'as open' to allow another record to extend it. The child record uses 'extends', calls 'super(...)' in its constructor, and overrides operators with the mandatory 'override' keyword.\n\nTHE AS OPEN OPT-IN\nWithout 'as open' on the base, attempting to extend triggers E05030 (not open to be extended) — same rule as classes. Records are NOT open just because they are records; they are closed by default like every other genus (see Q101).\n\nCHILD-OF-RECORD STRUCTURE\nA child record:\n- declares 'extends ParentRecord' (or 'is ParentRecord')\n- calls 'super(arg, ...)' from its constructor to initialise the inherited fields\n- overrides parent operators with 'override operator <symbol> as pure' (override is mandatory; see Q102, Q570)\n- can call 'super.<symbol>(...)' inside an override to compose parent behaviour with child fields\n- typically declares 'default operator ?' so its own fields gain set/unset semantics\n\nFIELD INHERITANCE — PUBLIC AND VISIBLE\nRecord fields are public (Q900, Q1060). When a child record extends a parent record, the parent's public fields remain public on instances of the child. Direct access works the same way:\n  child.parentField    OK — public, inherited\n  child.childField     OK — public, declared\n\nOPERATOR OVERRIDE CHAINING WITH super.<op>(...)\nThe canonical pattern for the comparator <=> on a child record is: compute the parent's result first, then refine with the child's own fields. The same pattern applies to $ (string) and #? (hashcode):\n  override operator <=> as pure\n    -> other as ChildRecord\n    <- rtn as Integer: super.<=>(other)\n    if rtn == 0\n      rtn: childField1 <=> other.childField1\n    if rtn == 0\n      rtn: childField2 <=> other.childField2\n\nFor $ the convention is to append the child's representation:\n  override operator $ as pure\n    <- rtn as String: `${super.$()} ChildRecord(${childField1}, ${childField2})`\n\nFor #? the convention is to xor with the child's hashes:\n  override operator #? as pure\n    <- rtn as Integer: super.#?() xor #?childField1 xor #?childField2\n\nMUTABILITY IS PRESERVED ACROSS THE HIERARCHY\nLike all EK9 records, inherited fields are mutable. Mutability is gated by 'as pure' on the calling function/method, not on the type, so the parent-child pair preserves the Liskov Substitution Principle (Q97).\n\nWHY OPERATOR OVERRIDE BUT NOT METHOD OVERRIDE?\nRecords cannot have methods at all — only constructors and operators (Q97, E07290). So inheritance on records is about extending fields and operators, never adding behaviour as methods. If a chain needs behaviour, switch the construct to a class.\n\nSee Q97 for class-vs-record overview. See Q101 for closed-by-default rationale. See Q102 for 'as open' on all genera. See Q570 for override mechanics. See Q900 for the public/private field rule. See Q876 for record basics. See Q98 for record operators. See Q116 for default operator. See Q241 for mutation operators.","ek9Example":"defines module qa.classesandoop.recordinheritance\n\n  defines record\n\n    <?-\n      Base record marked 'as open' so SimpleRecord may extend it.\n      Without 'as open' the compiler rejects the extension (E05030).\n    -?>\n    BaseRecord as open\n      anything <- 90\n\n      BaseRecord() as pure\n        -> anything as Integer\n        this.anything :=: anything\n\n      default operator <=>\n\n      default operator ?\n\n      default operator $\n      default operator #?\n\n    <?-\n      Child record extending BaseRecord.\n      - 'extends BaseRecord' inherits the public 'anything' field.\n      - 'super(anyValue)' initialises the inherited field via the parent constructor.\n      - 'override operator <=>' is mandatory (E05120 otherwise).\n      - 'super.<=>(other)' composes the parent comparison with the child's fields.\n      - 'default operator ?' synthesises set/unset semantics across all fields.\n    -?>\n    SimpleRecord extends BaseRecord\n      field1 <- 0\n      count as Integer?\n      check as Integer: 1\n\n      SimpleRecord() as pure\n        count :=? 0\n\n      SimpleRecord() as pure\n        ->\n          anyValue as Integer\n          field1 as Integer\n          count as Integer\n          check as Integer\n\n        super(anyValue)\n        this.field1 :=: field1\n        //'count' is declared uninitialised ('count as Integer?'), so first assignment in a pure constructor uses the guarded ':=?' (':=' reassignment is disallowed in 'pure'; ':=:' deep-copy requires an already-initialised target).\n        this.count :=? count\n        this.check :=: check\n\n      override operator <=> as pure\n        -> other as SimpleRecord\n        <- rtn as Integer: super.<=>(other)\n\n        if rtn == 0\n          rtn: field1 <=> other.field1\n        if rtn == 0\n          rtn: count <=> other.count\n        if rtn == 0\n          rtn: check <=> other.check\n\n      default operator ?\n\n      override operator $ as pure\n        <- rtn as String: `${super.$()} SimpleRecord(${field1}, ${count}, ${check})`\n\n      override operator #? as pure\n        <- rtn as Integer: super.#?() xor #?field1 xor #?count xor #?check\n\n  defines program\n\n    RecordInheritanceDemo()\n      stdout <- Stdout()\n\n      // 4-argument constructor needs named args (E11062 otherwise)\n      r1 <- SimpleRecord(anyValue: 10, field1: 1, count: 2, check: 3)\n\n      // Inherited public field 'anything' is directly accessible\n      stdout.println(`r1.anything: ${r1.anything}`)\n      stdout.println(`r1.field1:   ${r1.field1}`)\n      stdout.println(`r1.check:    ${r1.check}`)\n\n      // The overridden $ operator composes parent + child output\n      stdout.println(`r1: ${r1}`)\n\n      // The overridden <=> operator chains super.<=> with child fields\n      r2 <- SimpleRecord(anyValue: 10, field1: 1, count: 2, check: 3)\n      r3 <- SimpleRecord(anyValue: 10, field1: 1, count: 2, check: 4)\n      stdout.println(`r1 <=> r2: ${r1 <=> r2}`)\n      stdout.println(`r1 <=> r3: ${r1 <=> r3}`)\n\n      // Inherited field is mutable — mutability is gated by 'as pure' on the\n      // calling function, not on the type. Inside this (non-pure) program we\n      // may freely modify any inherited or declared field.\n      r1.anything: 999\n      stdout.println(`After mutation r1.anything: ${r1.anything}`)","migrationContext":"Java: records (Java 16+) are implicitly final — record-to-record inheritance is impossible. Kotlin: data classes cannot be open/inherited; the language explicitly disallows it. Rust: structs cannot inherit at all (no inheritance, period). Go: struct embedding is composition, not inheritance. Swift: structs are value types and cannot be subclassed. EK9 is unusual in allowing record-to-record inheritance via the same 'as open' opt-in mechanism as classes. This deliberately keeps records as data carriers while permitting layered data hierarchies — useful for event-sourcing, DTO families, and value-type taxonomies where a base shape is extended with extra fields and operator refinements. The override-mandatory rule (E05120) makes the inheritance explicit and prevents accidental shadowing.","keywords":["#?","$","<=>","BaseRecord","as open","comparator","data class hierarchy","extends","inheritance","open","operator","override","record","subclass","subrecord","super","value type hierarchy"],"primaryTopics":["record inheritance","as open record","operator override super","record extends"],"typicalErrors":[{"error":"E05030","correct":"BaseRecord as open","incorrect":"BaseRecord","explanation":"Without 'as open' on the base, SimpleRecord cannot extend BaseRecord. EK9 records are closed-by-default just like classes. The fix is to add 'as open' to the base record. See ek9 -h E05030 for details."},{"error":"E05120","correct":"override operator <=> as pure","incorrect":"operator <=> as pure","explanation":"When a child record re-declares an operator that exists on the parent, the 'override' keyword is mandatory. Omitting it triggers E05120. This applies to <=>, $, #?, and any operator inherited from the parent. See ek9 -h E05120 for details."},{"error":"E50010","correct":"SimpleRecord extends BaseRecord","incorrect":"SimpleClass extends BaseRecord","explanation":"A class cannot extend a record because they are different construct genera. Classes extend classes; records extend records. The compiler rejects cross-genus inheritance. See ek9 -h E50010 for details."},{"error":"E07290","correct":"default operator","incorrect":"describe() <- rtn as String: ...","explanation":"Records cannot have methods — only constructors and operators. Even a child record cannot add a method that didn't exist on the parent. If you need behaviour, switch the chain to classes. See ek9 -h E07290 for details."}],"companions":[],"oracleToolHint":{"tool":"ek9_query_effective_api","intent":"record inheritance","description":"Query the effective API of a child record at file:line to see declared + inherited fields with source attribution, plus operator overrides with the parent-method anchor for chain navigation."}}
{"id":1298,"category":"Concurrency","question":"I got E08252 NESTED_ENTER_DIFFERENT_LOCK — what does 'lock-order cycle' mean and how do I fix it?","url":"https://ek9.io/qa/QA1298.html","alternatePhrasings":["How do I fix E08252 deadlock cycle?","What is a lock-order cycle in EK9?","EK9 says my locks form a cycle — what now?","Two MutexLocks deadlock at compile time — how to restructure?","How to fix nested enter different lock identity?","Lock acquisition order error E08252"],"answer":"E08252 fires when EK9's deadlock detector finds two or more places in your program that acquire the same set of MutexLocks in OPPOSITE orders, forming a cycle. At runtime, two threads running these paths concurrently can deadlock — each holds one lock and waits for the other.\n\nWHAT THE ERROR MEANS\nThe analyzer builds a workspace-wide precedence graph: every time your code does `lockA.enter(...)` with `lockB.enter(...)` nested inside, an edge lockA → lockB is recorded. A CYCLE in this graph (e.g. lockA → lockB AND lockB → lockA somewhere) is a deadlock waiting to happen. The error message names the participating locks and renders the cycle path: 'lockA → lockB → lockA'.\n\nNOT TRIGGERED BY SINGLE-DIRECTION NESTING (PROVABLY-ORDERABLE LOCKS ONLY)\nIf only ONE function nests lockB inside lockA (and no other code ever does the reverse), there is no cycle — that code is safe, PROVIDED the two locks are provably orderable: different types, or different field declarations. E08252 only fires when at least two places contradict each other.\n\nIMPORTANT CAVEAT: this 'single direction is safe' rule does NOT extend to two locks of the SAME type on caller- or runtime-determined objects — two different same-type MutexLock parameters, or the same lock field on two different objects (the from.getLock()/to.getLock() bank transfer, two instances of one class, dining-philosophers forks). Those are interchangeable at the call site, so a single static direction can instantiate as both orders at runtime; EK9 rejects them even with no reverse site — that is E08255 (UNPROVABLE_LOCK_ORDER), not E08252. The fix is the same: one lock over an owning record.\n\nTHREE WAYS TO FIX\n\n1. UNIFY UNDER ONE LOCK\nIf both resources genuinely need atomic protection together, put them in a record and guard with ONE MutexLock. The MutexKey body can mutate every field of the record atomically. This is the canonical fix when joint atomicity is required (e.g. account-to-account transfer).\n\n  defines record\n    Balances\n      source as Integer: 0\n      target as Integer: 0\n      default operator ?\n\n  defines component\n    TransferService\n      accountLock as MutexLock of Balances: MutexLock(Balances())\n\n      transfer()\n        -> amount as Integer\n        key <- (amount) is MutexKey of Balances as function\n          lockedItem.source: lockedItem.source - amount\n          lockedItem.target: lockedItem.target + amount\n        require accountLock.enter(key)\n\n2. SEQUENTIAL — NEVER NESTED\nIf the two concerns don't need joint atomicity, acquire locks one at a time. Release the first before taking the second. Same code path, two enters, no nesting.\n\n  require lockA.enter(keyA)   //completes and releases\n  require lockB.enter(keyB)   //independent operation\n\n3. PICK A SINGLE OWNER FOR EACH CONCERN\nIf two methods on the same component want different locks, give each method exactly one lock. Different concerns → different methods → different locks. No method ever holds both.\n\nWHY EK9 DETECTS THIS\nLock-order cycle deadlocks are notoriously hard to reproduce — they only manifest under specific thread interleavings. Catching them at compile time eliminates a whole class of production bugs. The precedence-graph approach is the same model the Linux kernel uses at runtime (lockdep); EK9 brings it forward to compile time thanks to the closed-world type system.\n\nSee Q158 for MutexLock basics (which shows the canonical LockableAddressSet pattern). See Q1299 for the cross-thread variant (E08253). See Q1300 for multi-lock design patterns.","ek9Example":"defines module qa.concurrency.deadlock.cycle\n\n  defines record\n\n    Balances\n      source as Integer: 100\n      target as Integer: 0\n      default operator ?\n\n  defines component\n\n    //Canonical fix: ONE MutexLock guards a record containing both\n    //resources. The MutexKey body mutates both fields atomically;\n    //no second lock exists, so no cycle can form.\n    TransferService\n      accountLock as MutexLock of Balances: MutexLock(Balances())\n\n      transfer()\n        ->\n          amount as Integer\n\n        transferKey <- (amount) is MutexKey of Balances as function\n          //Both fields mutated atomically under one lock.\n          lockedItem.source: lockedItem.source - amount\n          lockedItem.target: lockedItem.target + amount\n\n        require accountLock.enter(transferKey)\n\n      default operator ?\n\n  defines program\n\n    LockOrderCycleFix()\n      stdout <- Stdout()\n      service <- TransferService()\n      service.transfer(25)\n      stdout.println(\"transfer complete — no deadlock possible\")","migrationContext":"Java: deadlocks discovered at runtime via thread dumps and stress testing. Tools like FindBugs/SpotBugs flag some cases but can't see polymorphic dispatch or call chains. Go: deadlock detector in the runtime fires only when ALL goroutines block — partial deadlocks slip through. Rust: lock-order checked by convention; std library has no compile-time detection. EK9: workspace-wide static cycle detection on the lock precedence graph; refuses to compile if a cycle exists across any combination of call paths.","keywords":["E08252","MutexLock","atomic","concurrent","cycle","deadlock","enter","fix","lock","mutex","nested","order","race","restructure","transfer"],"primaryTopics":["deadlock","lock-order","cycle detection"],"typicalErrors":[],"companions":[]}
{"id":1299,"category":"Concurrency","question":"I got E08253 CROSS_THREAD_SAME_LOCK — what is the thread boundary issue and how do I fix it?","url":"https://ek9.io/qa/QA1299.html","alternatePhrasings":["How do I fix E08253 cross-thread deadlock?","Same lock across thread boundary error in EK9","Why does | async cause deadlock with MutexLock?","Signals handler cannot reacquire same lock","TCP handler deadlocks on outer lock","MutexLock not reentrant across threads"],"answer":"E08253 fires when the SAME MutexLock is acquired on TWO DIFFERENT threads with the outer hold still in scope. Even though MutexLock is reentrant on the SAME thread (re-entering legally), it is NOT reentrant across threads — the second thread blocks waiting for the first to release. If the first thread is itself waiting on the second to complete (very common with async dispatch), they deadlock.\n\nWHERE THE THREAD BOUNDARY COMES FROM\nEK9 creates a thread boundary at three syntactic points:\n  1. TCP.accept(handler) — accept loop dispatches the handler on a new connection thread.\n  2. Signals.register(name, handler) — signal dispatcher invokes the handler from its dispatch thread.\n  3. Stream pipelines with `| async` — the async stage dispatches each element to a thread-pool worker.\nAnywhere else (regular function calls, the `| call` stream stage) stays on the same thread.\n\nTHE COMMON REFACTORING TRAP\nThe deadlock often appears after refactoring. A method holds a lock, pushes some work into a helper, the helper grows, eventually someone 'speeds things up' by adding `| async`. The async worker captures a reference to the lock and tries to enter it — by then no one remembers the outer hold is still active. EK9 catches this through the substitution chain even when the helper is many call frames deep.\n\nTHREE WAYS TO FIX\n\n1. CHANGE `| async` TO `| call` IF THREAD POOLING ISN'T NEEDED\nThe `| call` stream stage runs each element on the same thread synchronously. Same lock reacquisition is reentrant and legal:\n\n  outerKey <- (sharedLock) is MutexKey of Integer as function\n    cat workers | call | map with toLine > stdout   //SAFE — same thread\n  require sharedLock.enter(outerKey)\n\n2. MOVE THE ENTER() OUT OF THE ASYNC BOUNDARY\nIf you genuinely need async parallelism, don't have the worker reacquire the same lock. Do the locked work BEFORE dispatching to async, then pass the immutable result through the pipeline.\n\n  preparedItems as List of Item: List()\n  key <- (preparedItems) is MutexKey of Integer as function\n    preparedItems += extractSnapshot()   //all locked work here\n  require sharedLock.enter(key)\n\n  cat preparedItems | async with processItem   //no lock needed across boundary\n\n3. USE A DIFFERENT LOCK PER WORKER\nIf the async workers must protect shared state, give each worker its OWN lock (different MutexLock for that concern), and never hold the outer lock when the workers run. The outer pipeline coordinates without the contended lock.\n\nWHY EK9 DETECTS THIS\nClassic Java code that uses ExecutorService.submit() with a synchronized block on the same lock will hang at runtime. The bug is often only visible under load. EK9 makes the thread-boundary visible in the syntax — `| async` is the keyword — and propagates lock identity through call chains and substitution, so the compiler catches the reacquisition even when the worker is several refactoring layers away from the outer hold.\n\nSee Q158 for MutexLock basics (which shows the canonical LockableAddressSet pattern). See Q159 for stream pipelines with async. See Q1298 for the same-thread lock-order cycle (E08252). See Q1300 for multi-lock design patterns.","ek9Example":"defines module qa.concurrency.cross.thread.fix\n\n  defines function\n\n    asyncWorkerFn() as abstract\n      <- rtn as Boolean?\n\n    toLine() as pure\n      -> ok as Boolean\n      <- rtn as String: $ok\n\n    //Canonical fix: do the locked work BEFORE the async boundary; the\n    //resulting snapshot is immutable enough to pass through the pipeline\n    //without needing the lock. Workers run on different threads but never\n    //touch the outer hold.\n    crossThreadSafe()\n      -> sharedLock as MutexLock of Integer\n\n      stdout <- Stdout()\n      snapshot as Integer: Integer()\n\n      readKey <- (snapshot) is MutexKey of Integer as function\n        snapshot :=: lockedItem\n      require sharedLock.enter(readKey)\n\n      results <- [ true, false, true ]\n      cat results | map with toLine > stdout","migrationContext":"Java: ExecutorService.submit() with a synchronized block on the same monitor — deadlocks at runtime, often only under load. Goroutines / channels — easy to spawn but lock interactions are the developer's responsibility. Python asyncio — explicit await; lock acquisition across awaits can deadlock with no compile-time warning. EK9: thread boundaries are syntactic (`| async`, TCP.accept, Signals.register); the compiler tracks the lock identity through any depth of call chain and refuses to compile if the same lock would be acquired on a different thread while still held.","keywords":["E08253","MutexLock","TCP","async","boundary","call","concurrency","cross-thread","deadlock","fix","handler","reentrant","refactor","signal","thread"],"primaryTopics":["cross-thread","deadlock","async safety"],"typicalErrors":[{"error":"E08253","correct":"stdout <- Stdout()\n      snapshot as Integer: Integer()\n\n      readKey <- (snapshot) is MutexKey of Integer as function\n        snapshot :=: lockedItem\n      require sharedLock.enter(readKey)\n\n      results <- [ true, false, true ]\n      cat results | map with toLine > stdout","incorrect":"worker <- (sharedLock) is asyncWorkerFn as function\n        innerKey <- () is MutexKey of Integer as function\n          stdout <- Stdout()\n          stdout.println(lockedItem)\n        rtn: sharedLock.enter(innerKey)\n      outerKey <- (worker) is MutexKey of Integer as function\n        stdout <- Stdout()\n        workers <- [ worker ]\n        cat workers | async | map with toLine > stdout\n      require sharedLock.enter(outerKey)","explanation":"The canonical fix does all locked work BEFORE the async boundary and passes immutable data through the pipeline. The incorrect form captures `sharedLock` into a worker and calls `sharedLock.enter(innerKey)` inside it, while `cat workers | async` dispatches that worker to a thread-pool thread and the outer still holds the lock via `sharedLock.enter(outerKey)`. MutexLock is reentrant only on the holding thread, so the worker blocks cross-thread and — if the caller waits on the pipeline — both deadlock. Use `| call` (same thread) if you must reacquire, or keep the lock off the async workers entirely. See ek9 -h E08253."}],"companions":[]}
{"id":1300,"category":"Concurrency","question":"How do I use multiple MutexLocks correctly in EK9 — when do I need one big lock vs many small ones?","url":"https://ek9.io/qa/QA1300.html","alternatePhrasings":["When to use multiple MutexLocks in EK9?","How to decide one lock vs many locks?","Coarse vs fine-grained MutexLock design","Best practices for multiple locks in EK9","How to coordinate multiple locks safely?","Multi-lock patterns to avoid deadlock"],"answer":"EK9 supports multiple MutexLocks — but you have to design them so the compiler can prove the program is deadlock-free. There are three idiomatic patterns; pick based on whether the resources need atomic coordination together.\n\nPATTERN 1: ONE LOCK PER INDEPENDENT CONCERN\nUse multiple locks when two parts of your state are independent — no operation ever needs both. Each method touches exactly one lock; locks are never nested across concerns.\n\n  defines component\n    InventorySystem\n      orderLock as MutexLock of Integer: MutexLock(Integer(0))\n      inventoryLock as MutexLock of Integer: MutexLock(Integer(0))\n\n      placeOrder() ... require orderLock.enter(orderKey)\n      adjustInventory() ... require inventoryLock.enter(invKey)\n\nThe deadlock detector sees no cross-lock nesting — no precedence-graph edges, no possible cycle. This is the cleanest pattern when your domain naturally separates.\n\nPATTERN 2: SEQUENTIAL ACQUISITION (RELEASE THEN ACQUIRE)\nWhen one function needs two locks at different times in its body, acquire and release each in turn — never hold both at once. The MutexKey callback completes (and releases) BEFORE the next enter() begins.\n\n  fn()\n    ->\n      lockA as MutexLock of Integer\n      lockB as MutexLock of String\n\n    keyA <- () is MutexKey of Integer as function\n      //work with lockA\n    keyB <- () is MutexKey of String as function\n      //work with lockB\n\n    require lockA.enter(keyA)   //completes and releases\n    require lockB.enter(keyB)   //independent of lockA\n\nNo overlap = no cycle. Works as long as the two operations are truly independent (no shared invariant that needs to span both critical sections).\n\nPATTERN 3: ONE LOCK OVER A UNIFIED RECORD\nWhen two resources MUST be mutated atomically together — the classic 'transfer money from A to B' case — wrap them in a record and protect with a single MutexLock. The MutexKey body mutates all fields atomically; you never need two locks.\n\n  defines record\n    AccountPair\n      sourceBalance as Integer: 100\n      targetBalance as Integer: 0\n      default operator ?\n\n  defines component\n    TransferService\n      accountLock as MutexLock of AccountPair: MutexLock(AccountPair())\n\n      transfer()\n        -> amount as Integer\n        key <- (amount) is MutexKey of AccountPair as function\n          lockedItem.sourceBalance: lockedItem.sourceBalance - amount\n          lockedItem.targetBalance: lockedItem.targetBalance + amount\n        require accountLock.enter(key)\n\nGranularity trade-off: this serialises every transfer through one lock. If contention is real, decompose differently — per-transaction-id locks, optimistic concurrency with version numbers, or partition by account-range. But measure first; coarse locking is fine for most workloads.\n\nWHAT TO AVOID\nThe pattern EK9 deliberately refuses to compile: nest one lock inside another's MutexKey body in a way that creates a cycle elsewhere in the program. Example to AVOID:\n\n  transferAtoB() ... lockA.enter(... lockB.enter(...))   //order A → B\n  transferBtoA() ... lockB.enter(... lockA.enter(...))   //order B → A — cycle!\n\nThe two functions together form a lock-order cycle: T1 running transferAtoB while T2 runs transferBtoA can deadlock. EK9 fires E08252.\n\nSAME-TYPE LOCKS ON DIFFERENT OBJECTS (E08255) — REJECTED EVEN WITHOUT A REVERSE SITE\nA subtler trap: nesting two locks of the SAME type whose objects are caller- or runtime-determined — the same lock field on two different objects (from.getLock() then to.getLock(), two instances of one class, dining-philosophers forks, collection elements), or two different same-type MutexLock parameters. Because the objects are interchangeable at the call site, a SINGLE static direction can instantiate as both orders at runtime — so EK9 rejects it with E08255 (UNPROVABLE_LOCK_ORDER) even when no opposite-order site exists. There is no 'always lock the lower-address one first' escape; the only fix is Pattern 3 (one lock over an owning record).\n\nDURABLE OR CROSS-INSTANCE STATE IS A TRANSACTION CONCERN, NOT A MUTEX CONCERN\nIf the data you are trying to coordinate is durable (database rows) or shared across processes/instances, an in-memory MutexLock cannot protect it at all — that is a transaction concern (use the database's transaction / optimistic-locking facilities), not something a lock can solve.\n\nThe acquisition-order convention pattern ('always lockA before lockB') is NOT trusted by the compiler. Even if your team is disciplined, the convention can break under refactoring pressure. The compiler wants the constraint enforced by code structure (one of the three patterns above), not by developer discipline.\n\nWHEN TO USE WHICH\n- Independent concerns, no joint atomicity → Pattern 1.\n- Two operations in one method but no overlapping atomicity → Pattern 2.\n- Joint atomicity required (one resource depends on another being valid) → Pattern 3.\n\nSee Q158 for MutexLock basics (which shows the canonical LockableAddressSet pattern). See Q1298 for E08252 cycle errors. See Q1299 for E08253 cross-thread errors.","ek9Example":"defines module qa.concurrency.multi.lock.patterns\n\n  defines record\n\n    //Pattern 3 demo: unified record under one lock for joint atomicity.\n    AccountPair\n      sourceBalance as Integer: 100\n      targetBalance as Integer: 0\n      default operator ?\n\n  defines component\n\n    TransferService\n      accountLock as MutexLock of AccountPair: MutexLock(AccountPair())\n\n      transfer()\n        -> amount as Integer\n\n        transferKey <- (amount) is MutexKey of AccountPair as function\n          lockedItem.sourceBalance: lockedItem.sourceBalance - amount\n          lockedItem.targetBalance: lockedItem.targetBalance + amount\n\n        require accountLock.enter(transferKey)\n\n      default operator ?\n\n    //Pattern 1 demo: two locks for two independent concerns. Neither\n    //method ever holds both — no precedence-graph edges, no cycle.\n    OrderInventory\n      orderLock as MutexLock of Integer: MutexLock(Integer(0))\n      inventoryLock as MutexLock of Integer: MutexLock(Integer(0))\n\n      placeOrder()\n        ->\n          orderId as Integer\n        orderKey <- (orderId) is MutexKey of Integer as function\n          lockedItem :=: lockedItem + orderId\n        require orderLock.enter(orderKey)\n\n      adjustInventory()\n        ->\n          delta as Integer\n        invKey <- (delta) is MutexKey of Integer as function\n          lockedItem :=: lockedItem + delta\n        require inventoryLock.enter(invKey)\n\n      default operator ?\n\n  defines program\n\n    MultiLockPatterns()\n      stdout <- Stdout()\n\n      //Pattern 3: joint atomicity through one lock.\n      service <- TransferService()\n      service.transfer(25)\n      stdout.println(\"Pattern 3: transfer complete\")\n\n      //Pattern 1: independent concerns, two locks, no nesting.\n      orders <- OrderInventory()\n      orders.placeOrder(42)\n      orders.adjustInventory(-1)\n      stdout.println(\"Pattern 1: independent operations complete\")","migrationContext":"Java: developers manage lock ordering by convention (e.g. always grab the lower-hashcode lock first). Tools like FindBugs/SpotBugs detect a subset; most acquisition-order bugs are caught only at runtime or via stress testing. Go / Rust: same convention-driven discipline; no language-level prevention. EK9: three structural patterns that the compiler can verify; the convention-driven approach is rejected because it doesn't survive refactoring.","keywords":["MutexLock","atomic","best","coarse","concurrent","deadlock","design","fine","grained","locks","multiple","mutex","pattern","practice","structure","transfer"],"primaryTopics":["multi-lock design","lock patterns","deadlock avoidance"],"typicalErrors":[],"companions":[]}
{"id":1301,"category":"Streams and Pipelines","question":"Why can't I sort or group a stream from Stdin, UDP or a TCP connection?","url":"https://ek9.io/qa/QA1301.html","alternatePhrasings":["What triggers E10050 BUFFERING_REQUIRES_BOUNDED?","Why does sort fail on an unbounded stream source in EK9?","How do I sort/group/tail/uniq a stream from a socket or Stdin?"],"answer":"Buffering operations (sort, group, tail, uniq) must see or accumulate over ALL of the input before producing output, so they require a BOUNDED (finite) source. Stdin, UDP and TCPConnection are UNBOUNDED — infinite IO streams with no defined end — so buffering one would consume memory without bound and never emit. EK9 rejects this at compile time (E10050).\n\nERROR\n  stdin <- Stdin()\n  cat stdin | sort > stdout        // ERROR: E10050\n\nFIX WITH head N (head bounds the stream)\n  cat stdin | head 1000 | sort > stdout   // OK\n\nFIX WITH bounded windows\n  while active\n    batch <- cat udpConnection | head 100 | collect as List of Packet\n    cat batch | sort > processor\n\nSTREAMING OPERATIONS ARE FINE ON UNBOUNDED SOURCES\n  cat stdin | filter by validLine | map with toEntry > stdout   // OK\n  cat udpConnection | map by handler > stdout                   // OK\n\nhead N is the mechanism that converts an unbounded stream to a bounded one.","ek9Example":"defines module qa.streams.unboundedbuffer\n\n  defines function\n\n    nonEmpty() as pure\n      -> line as String\n      <- rtn as Boolean: length line > 0\n\n  defines program\n\n    UnboundedStreamDemo()\n      stdin <- Stdin()\n      stdout <- Stdout()\n\n      //Streaming operations (filter/map) compose freely with an unbounded Stdin source.\n      cat stdin | filter by nonEmpty > stdout\n\n      //To sort/group/tail/uniq, 'head N' must bound the unbounded stream first.\n      first10Sorted <- List() of String\n      cat stdin | head 10 | sort > first10Sorted\n\n      stdout.println(`Captured: ${length first10Sorted}`)","migrationContext":"Java: Stream.sorted() on an infinite stream hangs at runtime, no compile check. Python: sorted(infinite_generator) hangs. Rust: collecting an infinite iterator to sort hangs. EK9: the compiler proves at compile time that buffering an unbounded source is impossible and requires 'head N' to bound it first.","keywords":["E10050","Stdin","TCP","UDP","backpressure","bounded","group","head","sort","stream","tail","unbounded","uniq"],"primaryTopics":["unbounded source","E10050","BUFFERING_REQUIRES_BOUNDED","head bounds stream"],"typicalErrors":[{"error":"E10050","correct":"      cat stdin | head 10 | sort > first10Sorted","incorrect":"      cat stdin | sort > first10Sorted","explanation":"Buffering operators like sort need a bounded source; stdin is unbounded, so add 'head N' before sort. See ek9 -h E10050 for details."}],"companions":[]}
{"id":1302,"category":"Code Quality","question":"Why does EK9 reject a single deeply-nested coalescing expression?","url":"https://ek9.io/qa/QA1302.html","alternatePhrasings":["What triggers E11013 EXCESSIVE_EXPRESSION_COMPLEXITY?","How do I fix an expression that is too complex in EK9?","Why must I extract intermediate variables from nested coalescing?"],"answer":"EK9 measures expression complexity independently of method complexity. Nested coalescing operators (?:, ??, <?, >?, <=?, >=?) carry an exponential 3^(depth-1) penalty, so three nested levels reach 17 points and exceed the 15-point threshold, raising E11013. The fix is to extract named intermediate variables (step1<-..., step2<-...), which flattens the depth to 1 each while producing an identical result.\n\nSee Q311 for quality checks.","ek9Example":"defines module qa.quality.expression.complexity\n\n  defines function\n\n    //THE FIX: extract intermediate named variables instead of one nested expression.\n    //Each coalescing operator is now at depth 1, so total cost is 1 + 2 + 2 = 5 (well under 15).\n    computeFallback() as pure\n      ->\n        a as Integer\n        b as Integer\n        c as Integer\n        d as Integer\n      <-\n        result as Integer?\n      step1 <- a ?: b\n      step2 <- step1 <? c\n      result: step2 >? d\n\n  defines program\n\n    ExpressionComplexityDemo()\n      stdout <- Stdout()\n      result <- computeFallback(a: Integer(), b: 2, c: 3, d: 4)\n      stdout.println(`Result is ${result}`)","migrationContext":"Java: no expression-level complexity analysis; nested ternaries compile silently. Kotlin/Swift: chained ?:/?? unlimited, no linter rule for coalescing depth. EK9: compile-time error forcing extraction into readable intermediate variables.","keywords":["E11013","coalescing","complexity","expression","extract","intermediate","quality","variable"],"primaryTopics":["expression complexity","E11013","intermediate variables"],"typicalErrors":[{"error":"E11013","correct":"      step1 <- a ?: b\n      step2 <- step1 <? c\n      result: step2 >? d","incorrect":"result <- ((a ?: b) <? c) >? d","explanation":"The single nested expression costs 1*9 + 2*3 + 2*1 = 17 points (3^(depth-1) per level), exceeding the 15-point expression threshold. Extracting each coalescing step into its own named variable drops every expression to depth 1 (1 + 2 + 2 = 5 points total), so none exceeds the threshold while the computed result is identical."}],"companions":[]}
{"id":1303,"category":"Code Quality","question":"Why does EK9 reject a call nested inside a throw expression?","url":"https://ek9.io/qa/QA1303.html","alternatePhrasings":["What triggers E11070 NESTED_CALL_IN_THROW?","Why can't I throw the result of a call chain directly?","How do I throw an exception produced by a factory call in EK9?"],"answer":"EK9 rejects a call nested inside a throw (E11070) when the thrown expression is itself a call applied to the result of another call (a higher-order chain such as throw getFactory(\"std\")(\"oops\")). The exception construction is hidden behind that chain. Extract each step into a local variable and throw the variable, so the exception is materialised and the throw site stays readable. The two valid forms are a direct constructor call (throw Exception(reason)) or a previously-declared variable (throw ex).\n\nSee Q311 for quality checks.","ek9Example":"defines module qa.quality.throw.nested.call\n\n  defines function\n\n    ExceptionFactory() as pure abstract\n      -> reason as String\n      <- builtException as Exception?\n\n    StandardExceptionFactory() is ExceptionFactory as pure\n      -> reason as String\n      <- builtException as Exception: Exception(reason)\n\n    getExceptionFactory()\n      -> kind as String\n      <- chosen as ExceptionFactory: StandardExceptionFactory\n      if kind == \"alt\"\n        chosen: StandardExceptionFactory\n\n    //FIX: extract the factory selection and the exception into local\n    //variables, then throw the variable. No call is nested in another\n    //call at the throw site.\n    raiseFailure()\n      -> kind as String\n      factory <- getExceptionFactory(kind)\n      ex <- factory(\"operation failed\")\n      throw ex\n\n  defines program\n\n    NestedCallInThrowDemo()\n      stdout <- Stdout()\n      try\n        raiseFailure(\"std\")\n      catch\n        -> caught as Exception\n        stdout.println(caught.reason())","migrationContext":"Java: throwing the result of a factory chain is unchecked by the compiler. Python/Go: no detection. EK9: compile-time error (E11070) requiring the thrown exception to be a direct constructor call or a previously-declared variable, never a call applied to a call.","keywords":["E11070","call","exception","extract","nested","quality","throw"],"primaryTopics":["nested call in throw","E11070","exception extraction"],"typicalErrors":[{"error":"E11070","correct":"      factory <- getExceptionFactory(kind)\n      ex <- factory(\"operation failed\")\n      throw ex","incorrect":"      throw getExceptionFactory(kind)(\"operation failed\")","explanation":"Throwing a call applied to another call's result hides the exception construction at the throw site — extract the factory and exception into local variables and throw the variable. See ek9 -h E11070 for details."}],"companions":[]}
{"id":1304,"category":"Code Quality","question":"How do I fix E11043 when a method injects too many distinct types?","url":"https://ek9.io/qa/QA1304.html","alternatePhrasings":["What triggers E11043 EXCESSIVE_INJECTION_TYPES_PER_METHOD?","Why does EK9 reject a method that injects more than 4 distinct types?","How do I group method-local '!' injections behind a facade?"],"answer":"A single method may inject at most 4 distinct types via the method-local '!' suffix. Injecting a 5th distinct type triggers E11043 (EXCESSIVE_INJECTION_TYPES_PER_METHOD) - the method has too many collaborators to understand or test.\n\nFix: group the related dependencies behind ONE dedicated facade component. Register the facade in the application, then inject the single facade type with '!'. The method now has one collaborator.\n\nSee Q203 for application wiring. See Q960 for the per-component injection-field limit (E11040). See Q311 for quality checks.","ek9Example":"defines module qa.quality.injection.types\n\n  defines component\n\n    Repository as abstract\n      findAll() as abstract\n        <- rtn as List of String?\n\n      default operator ?\n\n    InMemoryRepo extends Repository\n      override findAll()\n        <- rtn <- List() of String\n        rtn += \"order-1\"\n\n      default operator ?\n\n    Logger as abstract\n      log() as abstract\n        -> message as String\n\n      default operator ?\n\n    ConsoleLogger extends Logger\n      override log()\n        -> message as String\n        stdout <- Stdout()\n        stdout.println(message)\n\n      default operator ?\n\n    Mailer as abstract\n      send() as abstract\n        -> message as String\n\n      default operator ?\n\n    SmtpMailer extends Mailer\n      override send()\n        -> message as String\n        stdout <- Stdout()\n        stdout.println(`sending: ${message}`)\n\n      default operator ?\n\n    //The facade groups the three collaborators behind ONE injectable type.\n    //A method injects only the facade, keeping distinct injected types low.\n    OrderFacade as abstract\n      placeOrder() as abstract\n        <- rtn as String?\n\n      default operator ?\n\n    DefaultOrderFacade extends OrderFacade\n      repo as Repository!\n      logger as Logger!\n      mailer as Mailer!\n\n      override placeOrder()\n        <- rtn as String: \"placed\"\n        orders <- repo.findAll()\n        logger.log(`Found ${length orders} orders`)\n        mailer.send(\"order confirmation\")\n\n      default operator ?\n\n  defines application\n\n    OrderApp\n      register InMemoryRepo() as Repository\n      register ConsoleLogger() as Logger\n      register SmtpMailer() as Mailer\n      register DefaultOrderFacade() as OrderFacade\n\n  defines program\n\n    //CORRECT: the method injects ONE facade type, not five distinct types.\n    FacadeDemo() with application of OrderApp\n      stdout <- Stdout()\n\n      facade as OrderFacade!\n\n      result <- facade.placeOrder()\n      stdout.println(`Order ${result} via single facade injection`)","migrationContext":"Java: Spring allows unlimited @Autowired locals; over-injection detected only by SonarQube/ArchUnit if configured. C#/.NET: Seemann recommends a low collaborator count but it is advisory. Go: Wire has no per-function limit, caught only in review. EK9: compiler-enforced limit of 4 distinct injected types per method, fixed by a facade component.","keywords":["E11043","application","component","facade","injection","method","quality","register"],"primaryTopics":["facade component","E11043","method-local injection"],"typicalErrors":[],"companions":[]}
{"id":1305,"category":"Web Services","question":"Why does EK9 reject two service methods that share the same HTTP path?","url":"https://ek9.io/qa/QA1305.html","alternatePhrasings":["What triggers E02070 duplicate service path/operation?","Why can't two GET methods map to the same URI in EK9?","How do I fix a duplicated service endpoint path in EK9?"],"answer":"Each HTTP verb plus URI path must map to exactly one handler method. When two methods on a service share the same verb and structurally identical path, EK9 raises E02070 at compile time - there is no way to know which handler should run for that URL.\n\nPath parameters are compared STRUCTURALLY, not by name: :/{id} and :/{userId} are the SAME path, so renaming a path variable does NOT resolve the conflict. The fix is to make the paths genuinely distinct (a different literal segment, an extra segment, or a different HTTP verb).\n\nSee Q684 for valid URI path rules. See Q946 for path-variable-to-parameter matching.","ek9Example":"defines module qa.webdeep.duplicatepath\n\n  defines constant\n\n    JSON_CONTENT_TYPE <- \"application/json\"\n    ENGLISH_LANGUAGE <- \"en\"\n\n  defines service\n\n    <?-\n      Service demonstrating distinct endpoints that avoid E02070.\n      Each HTTP verb plus path combination is unique. Note that\n      :/{id} and :/{name}/profile differ structurally (extra segment),\n      so they are NOT duplicates even though both contain a path variable.\n    -?>\n    UserService :/users open\n\n      //GET /users/{id} — lookup by identifier\n      findById() as GET for :/{id}\n        -> id as String\n        <- response as HTTPResponse: (capturedId: id) with trait HTTPResponse\n          override content()\n            <- rtn as String: `{\"id\": \"${capturedId}\"}`\n          override status() as pure\n            <- rtn as Integer: 200\n          override contentType() as pure\n            <- rtn as String: JSON_CONTENT_TYPE\n          override cacheControl() as pure\n            <- rtn as String: \"no-cache\"\n          override contentLanguage() as pure\n            <- rtn as String: ENGLISH_LANGUAGE\n          default operator ?\n\n      //GET /users/{name}/profile — distinct path (extra literal segment),\n      //so no E02070 conflict with findById even though both have a variable.\n      byName() as GET for :/{name}/profile\n        -> name as String\n        <- response as HTTPResponse: (capturedName: name) with trait HTTPResponse\n          override content()\n            <- rtn as String: `{\"name\": \"${capturedName}\"}`\n          override status() as pure\n            <- rtn as Integer: 200\n          override contentType() as pure\n            <- rtn as String: JSON_CONTENT_TYPE\n          override cacheControl() as pure\n            <- rtn as String: \"max-age=60\"\n          override contentLanguage() as pure\n            <- rtn as String: ENGLISH_LANGUAGE\n          default operator ?\n\n  defines application\n\n    UserApp\n      register UserService()\n\n  defines program\n\n    DuplicateServicePathDemo()\n      stdout <- Stdout()\n      stdout.println(\"Distinct service endpoints (no E02070):\")\n      stdout.println(\"  GET /users/{id}\")\n      stdout.println(\"  GET /users/{name}/profile\")","migrationContext":"Java Spring: two @GetMapping(\"/x\") methods compile fine and only collide at startup with an AmbiguousMappingException - a runtime failure. Python Flask: a duplicate @app.route raises only when the app boots. Go: last route silently wins. EK9: the duplicate endpoint is a compile-time error (E02070), so the ambiguous routing can never ship.","keywords":["E02070","GET","URI","duplicate","endpoint","http","path","route","service","webservice"],"primaryTopics":["duplicate service path","E02070","service routing"],"typicalErrors":[{"error":"E02070","correct":"byName() as GET for :/{name}/profile","incorrect":"byNick() as GET for :/{nick}","explanation":"Two GET methods both mapping to :/{id} and :/{nick} are the SAME endpoint because path variables match structurally, not by name - so EK9 cannot pick a handler and raises E02070. Make the paths genuinely distinct (add a literal segment such as :/{name}/profile, or use a different HTTP verb). See ek9 -h E02070 for details."}],"companions":[]}
{"id":1306,"category":"Syntax and Structure Rules","question":"Why can't a local construct share a name with a referenced (imported) type in EK9?","url":"https://ek9.io/qa/QA1306.html","alternatePhrasings":["What triggers E03010 CONSTRUCT_REFERENCE_CONFLICT?","Why does importing List then defining a local class List fail to compile?","How do I resolve a name collision between a references import and a local type?"],"answer":"EK9 refuses to pick a winner when a locally-defined construct (class, record, trait, function) shares its name with a type brought in via a 'references' block. Both the import and the local definition want the same short name, so any later use of that name is ambiguous - the compiler rejects it immediately with E03010 rather than silently choosing one.\n\nFix: rename the local construct to a distinct name (e.g. ItemList instead of List). The referenced type keeps its short name, the local type gets its own, and every use resolves to exactly one symbol. Alternatively, drop the reference and use the fully qualified 'module::Type' form inline when the imported type is needed.\n\nSee Q726 for references block syntax. See Q832 for the design rationale.","ek9Example":"defines module qa.syntaxrules.referencecollision\n\n  //'List' is imported via references and keeps that short name.\n  references\n    org.ek9.lang::List\n\n  //THE FIX: the local construct is named distinctly (ItemList), so it does\n  //NOT collide with the referenced 'List'. Naming this class 'List' would\n  //trigger E03010 CONSTRUCT_REFERENCE_CONFLICT.\n  defines class\n\n    ItemList\n      items as List of String?\n\n      ItemList()\n        items: List() of String\n\n      ItemList()\n        -> initial as String\n        items: List() of String\n        items += initial\n\n      add()\n        -> item as String\n        items += item\n\n      size()\n        <- rtn as Integer: length items\n\n      default operator ?\n\n  defines program\n\n    ReferenceCollisionDemo()\n      stdout <- Stdout()\n\n      //'List' resolves unambiguously to the referenced org.ek9.lang::List.\n      basket <- ItemList(\"apple\")\n      basket.add(\"pear\")\n\n      stdout.println(`Basket holds ${basket.size()} items`)","migrationContext":"Java: wildcard imports (import java.util.*) silently shadow local types; the conflict surfaces only when two imports expose the same name. Python: 'from module import *' overwrites local names with no warning. C#: 'using' directives let a local type silently win over an imported one. EK9: neither the reference nor the local takes priority - the collision is a compile-time error (E03010) and the developer must choose a distinct name.","keywords":["E03010","collision","conflict","import","local","name","references","rename","syntax"],"primaryTopics":["reference vs local collision","E03010","construct naming"],"typicalErrors":[{"error":"E03010","correct":"  references\n    org.ek9.lang::List\n\n  //THE FIX: the local construct is named distinctly (ItemList), so it does","incorrect":"references\n  org.ek9.lang::List\ndefines class\n  List","explanation":"Importing 'List' via references and then defining a local class also named 'List' makes the short name 'List' ambiguous - neither the import nor the local definition takes priority, so EK9 raises E03010. Rename the local construct (ItemList) so each name resolves to exactly one symbol, or drop the reference and use the fully qualified org.ek9.lang::List inline. See ek9 -h E03010 for details."}],"companions":[]}
{"id":1307,"category":"Syntax and Structure Rules","question":"Why does EK9 reject the same symbol listed twice in a references block?","url":"https://ek9.io/qa/QA1307.html","alternatePhrasings":["What triggers E03020 conflicting references in EK9?","Why can't I import the same type twice in EK9?","How do I fix a duplicate reference in an EK9 references block?"],"answer":"Each symbol may appear at most once in a module's references block. Listing the same module.path::Symbol twice (or two references that resolve to the same short name) triggers E03020 - conflicting references - because the second entry is redundant and usually signals a copy-paste mistake.\n\nThe fix is simple: keep one reference and delete the duplicate. If two genuinely different modules each export a type with the same short name, you cannot reference both; keep the more frequently used one in the references block and use the fully qualified module.path::Symbol form inline for the other.\n\nSee Q726 for module reference syntax. See Q851 for why references need the :: qualifier.","ek9Example":"defines module qa.syntax.duplicate.reference\n\n  //THE FIX: each symbol appears in the references block exactly once.\n  //Listing 'org.ek9.lang::List' twice would trigger E03020 (conflicting references).\n  references\n    org.ek9.lang::List\n\n  defines program\n\n    DuplicateReferenceDemo()\n      stdout <- Stdout()\n\n      //Short-name access via the single, non-duplicated reference.\n      names <- List() of String\n      names += \"Alice\"\n      names += \"Bob\"\n      stdout.println(`Names count: ${length names}`)","migrationContext":"Java: duplicate import statements compile and are at most an IDE/checkstyle warning. Python: a duplicate import silently re-binds the name with no diagnostic. Go: an unused import is an error but a redundant duplicate is deduplicated by tooling. Rust: a duplicate 'use' is an error (E0252/E0254). EK9: a duplicate references entry is a hard compile-time error (E03020) caught in the REFERENCE_CHECKS phase, so the redundancy can never reach a build.","keywords":["E03020","conflict","duplicate","import","module","qualifier","reference","references","syntax"],"primaryTopics":["duplicate references","E03020","references block"],"typicalErrors":[{"error":"E03020","correct":"references\n    org.ek9.lang::List","incorrect":"references\n    org.ek9.lang::List\n    org.ek9.lang::List","explanation":"The same symbol 'List' is listed twice in the references block, so EK9 reports E03020 - the second entry conflicts with the first and is redundant. List every referenced symbol exactly once; if two different modules export the same short name, keep one in the references block and use the fully qualified module.path::Symbol form inline for the other. See ek9 -h E03020 for details."}],"companions":[]}
{"id":1308,"category":"Syntax and Structure Rules","question":"Why does my references statement fail with E03030 reference does not resolve?","url":"https://ek9.io/qa/QA1308.html","alternatePhrasings":["What triggers E03030 in EK9?","Why can't the compiler find the type named in my references block?","How do I fix a references entry that points at a module or type that does not exist?"],"answer":"E03030 means a 'references' entry uses a fully-qualified name (module.path::SymbolName) whose module path or symbol name the compiler cannot find. The format is valid (it has the '::' separator, so it is not E01010), but nothing in the compile path actually resolves to it.\n\nCOMMON CAUSES\n1. Typo in the symbol name: org.ek9.lang::Lst instead of ::List.\n2. Typo in the module path: org.ek9.lng::List (missing 'a').\n3. The defining module is not in the compile path (companion file missing, dependency not added).\n4. The type lives in a different module than the one you named.\n\nHOW TO FIX\nVerify the exact module path and symbol name against the defining module's 'defines' sections, and make sure that module compiles and is part of the same workspace/compile path. Once the name matches a real exported symbol, the reference resolves and the short name becomes usable.\n\nDISTINCTION FROM RELATED ERRORS\nE01010 is a FORMAT error (the '::' qualifier is missing entirely). E03030 is a RESOLUTION error (the format is fine but the target does not exist at import time). E50001 is the same idea at USE time for a bare identifier rather than a references entry.\n\nSee Q726 for references syntax. See Q851 for the module qualifier rule.","ek9Example":"defines module qa.ref.consumer.resolve\n\n  //THE FIX: the references entry names a real, exported symbol on a real\n  //module path, so it resolves at REFERENCE_CHECKS time and the short\n  //name 'List' becomes usable. A typo such as 'Lst' or a wrong module\n  //path (org.ek9.lng) would fail to resolve and raise E03030.\n  references\n    org.ek9.lang::List\n\n  defines program\n\n    ReferenceResolveDemo()\n      stdout <- Stdout()\n\n      names <- List() of String\n      names += \"World\"\n      stdout.println(`Greeting list size: ${length names}`)","migrationContext":"Java: a wrong import (import com.example.Lst) is a compile error 'cannot find symbol' / 'package does not exist'. Python: a bad 'from module import Name' raises ImportError at runtime, not compile time. Go: an unresolved import path fails the build. Rust: 'use std::collections::Vc' for a missing item is a compile error E0432. EK9: a references entry that does not resolve is caught at compile time in REFERENCE_CHECKS as E03030.","keywords":["E03030","REFERENCE_CHECKS","import","module","qualifier","reference","resolve","syntax","typo"],"primaryTopics":["references resolution","E03030","module imports"],"typicalErrors":[{"error":"E03030","correct":"references\n    org.ek9.lang::List","incorrect":"references\n  org.ek9.lang::Lst","explanation":"The reference 'org.ek9.lang::Lst' is well-formed (it has the '::' qualifier) but no symbol named 'Lst' exists in module 'org.ek9.lang' - the real exported symbol is 'List'. Because the name does not resolve at import time, REFERENCE_CHECKS raises E03030. Fix the typo so the symbol name matches a real exported symbol, and ensure the defining module is in the compile path. See ek9 -h E03030 for details."}],"companions":[]}
{"id":1309,"category":"Generics","question":"Why does EK9 reject 'of TypeName' on a non-generic type like String?","url":"https://ek9.io/qa/QA1309.html","alternatePhrasings":["What triggers E04070 when using 'of' on a type?","Why can't I write String() of Integer in EK9?","How do I know which types accept an 'of' clause in EK9?"],"answer":"The 'of' clause supplies type arguments and is only valid on generic (template) types - those declared with 'of type T'. Applying 'of SomeType' to a non-generic type such as String, Integer or any plain class raises E04070 because the type has a fixed structure and has no type parameter to fill.\n\nGENERIC TYPES (accept 'of')\nBuilt-in: List of T, Dict of (K, V), Optional of T, Result of (OK, ERR).\nUser-defined: any class declared 'Container of type T'.\n\nNON-GENERIC TYPES (reject 'of')\nString, Integer, Float, Boolean, Character and any plain class without an 'of type T' declaration. These need no type argument, so an 'of' clause is meaningless.\n\nFIX\nDrop the 'of' clause for a non-generic type (just String()), or, if you need a parameterized container, use a generic type such as List() of String.\n\nThe opposite mistake - omitting 'of' on a generic type - raises E04080, and supplying the wrong number of type arguments raises E06020.\n\nSee Q194 for defining generic classes. See Q198 for built-in generics.","ek9Example":"defines module qa.generics.nongeneric\n\n  defines program\n\n    NonGenericOfDemo()\n      stdout <- Stdout()\n\n      // === CORRECT: 'of' on a GENERIC type ===\n      // List is declared 'of type T', so it accepts a type argument.\n\n      names <- List() of String\n      names += \"Alice\"\n      names += \"Bob\"\n      stdout.println(`List of String has ${length names} items`)\n\n      // === CORRECT: a non-generic type needs NO 'of' clause ===\n      // String has a fixed structure; constructing it takes no type argument.\n\n      greeting <- String(\"hello\")\n      stdout.println(`Plain String: ${greeting}`)\n\n      // === WRONG (commented out): 'of' on a non-generic type ===\n      // bad <- String() of Integer   // ERROR E04070: String is not generic\n\n      stdout.println(\"Use 'of' only on generic types like List, Dict, Optional\")","migrationContext":"Java: String<Integer> is a compile error too, but only because String is not declared generic - the message is 'type String does not take parameters'. C#: same, 'non-generic type cannot be used with type arguments'. Kotlin/Swift: angle-bracket arguments on a non-generic type are rejected by the parser/type checker. EK9: the 'of' clause is rejected at the EXPLICIT_TYPE_SYMBOL_DEFINITION phase with E04070, stating the type is not 'template/generic' in nature.","keywords":["E04070","List","String","generic","non-generic","of","parameter","parameterize","template","type"],"primaryTopics":["non-generic type","E04070","of clause"],"typicalErrors":[{"error":"E04070","correct":"names <- List() of String","incorrect":"names <- String() of Integer","explanation":"String is not a generic/template type, so it has no type parameter to fill - applying 'of Integer' to it raises E04070. Either drop the 'of' clause for the non-generic type (just String()), or use a genuinely generic type such as List() of String when you need a parameterized container. See ek9 -h E04070 for details."}],"companions":[]}
{"id":1310,"category":"Generics","question":"Why does EK9 reject a generic type named without 'of' in a declaration?","url":"https://ek9.io/qa/QA1310.html","alternatePhrasings":["What triggers E04080 TEMPLATE_TYPE_REQUIRES_PARAMETERIZATION?","Why can't I declare a parameter or variable just 'as List' in EK9?","How do I specify the element type when declaring a generic in EK9?"],"answer":"When a generic type such as List, Dict, Optional or Result appears in a type position - a parameter declaration, a return-value declaration, or a field/variable type - it must be fully parameterized with an 'of' clause. Naming the bare generic (e.g. 'as List') gives the compiler no element type to work with, so it raises E04080 (Template/Generic requires parameterization). The fix is to state the element type: 'as List of String', 'as Dict of (String, Integer)', 'as Optional of MyClass', or 'as Result of (Integer, String)'. This is distinct from E06010, which fires when a bare generic is constructed in an expression such as 'items <- List()'.\n\nSee Q194 for generic classes. See Q198 for built-in generics.","ek9Example":"defines module qa.generics.parameterization\n\n  defines function\n\n    //THE FIX: every generic in a type position carries an 'of' clause.\n    //The parameter and the return value are both fully parameterized.\n    headCount() as pure\n      -> names as List of String\n      <- rtn as Integer: length names\n\n    //Dict in a parameter position needs both key and value types.\n    lookupAge() as pure\n      ->\n        ages as Dict of (String, Integer)\n        who as String\n      <- rtn as Integer: ages.getOrDefault(who, Integer())\n\n  defines program\n\n    GenericParameterizationDemo()\n      stdout <- Stdout()\n\n      names <- List() of String\n      names += \"Alice\"\n      names += \"Bob\"\n      stdout.println(`Head count: ${headCount(names)}`)\n\n      ages <- {\"Alice\": 30, \"Bob\": 25}\n      stdout.println(`Age of Alice: ${lookupAge(ages, \"Alice\")}`)","migrationContext":"Java: 'List items' (raw type) compiles with only an unchecked warning, deferring failures to runtime ClassCastException. Kotlin/Swift: a bare generic in a type position is a compile error, matching EK9. Python: collections are untyped, so element type is never declared. EK9: a generic in any declaration position MUST be parameterized at compile time (E04080), there is no raw-type escape hatch.","keywords":["Dict","E04080","List","Optional","declaration","generic","of","parameterization","parameterized","raw-type","type-parameter"],"primaryTopics":["generic parameterization","E04080","of clause"],"typicalErrors":[{"error":"E04080","correct":"    headCount() as pure\n      -> names as List of String\n      <- rtn as Integer: length names","incorrect":"headCount()\n  -> names as List\n  <- rtn as Integer: length names","explanation":"Declaring a parameter as the bare generic 'List' gives the compiler no element type, so it raises E04080. A generic named in a type position (parameter, return value, or field) must be fully parameterized: add an 'of' clause such as 'as List of String'. See ek9 -h E04080 for details."}],"companions":[]}
{"id":1311,"category":"Constructor Delegation","question":"Why can't I call this() or super() inside a regular method?","url":"https://ek9.io/qa/QA1311.html","alternatePhrasings":["What triggers E05060 this()/super() outside a constructor?","Why does EK9 reject super() in a normal method?","How do I call a parent member from a method without super()?"],"answer":"this() and super() are constructor delegation CALLS. They are only valid as the first statement inside a constructor body, where they delegate to another constructor of the same class (this(...)) or to the parent constructor (super(...)). Using them as a call anywhere else - inside a regular method, a property initializer, or a function - raises E05060.\n\nEK9 distinguishes the CALL syntax from the member-ACCESS syntax:\n  this()  = call another constructor of this class (constructors only)\n  super() = call the parent constructor (constructors only)\n  this.x  = access a member of this instance (anywhere in the class)\n  super.x = access a parent member (anywhere in the class)\n\nIf you wanted to read or assign a member from inside a method, use the DOT form (this.field or super.method()). If you genuinely wanted to delegate, move the call into a constructor as its first statement.\n\nSee Q580 for this() delegation. See Q581 for super() delegation. See Q582 for delegation order (E05050).","ek9Example":"defines module qa.constructor.delegationscope\n\n  defines class\n\n    Animal as open\n      species <- String()\n\n      Animal()\n        -> species as String\n        this.species: species\n\n      species() as pure\n        <- rtn as String: species\n\n      default operator ?\n\n    //Pet uses super() correctly - only inside a constructor, as the first statement.\n    Pet extends Animal\n      petName <- String()\n\n      Pet()\n        -> petName as String\n        super(\"Unknown\")\n        this.petName: petName\n\n      //CORRECT: a regular method uses DOT access (this.petName), never the\n      //delegation CALL super(...). Methods cannot re-run a constructor.\n      rename()\n        -> newName as String\n        this.petName: newName\n\n      petName() as pure\n        <- rtn as String: petName\n\n      default operator ?\n\n  defines program\n\n    DelegationScopeDemo()\n      stdout <- Stdout()\n\n      pet <- Pet(\"Buddy\")\n      stdout.println(`${pet.petName()} is a ${pet.species()}`)\n\n      pet.rename(\"Max\")\n      stdout.println(`Renamed to ${pet.petName()}`)","migrationContext":"Java: this()/super() are also constructor-only and the compiler rejects them in methods, but the error is generic. Kotlin: super.method() is the access form and there is no this() call in methods. C#: this()/base() are constructor-initializer-only. EK9: compile-time error E05060 with an explicit hint to switch from CALL syntax () to ACCESS syntax (.), or move the delegation into a constructor.","keywords":["E05060","access","call","constructor","delegation","dot","method","super","this"],"primaryTopics":["constructor delegation","this/super outside constructor","E05060"],"typicalErrors":[{"error":"E05060","correct":"rename()\n        -> newName as String\n        this.petName: newName","incorrect":"rename()\n        -> newName as String\n        super(\"Reset\")\n        this.petName: newName","explanation":"super(...) is a constructor delegation CALL and may only appear as the first statement of a constructor, so calling it inside the rename() method raises E05060. A regular method cannot re-run the parent constructor. To touch members from a method use the DOT access form (this.petName or super.someMethod()); to delegate, move the call into a constructor. See ek9 -h E05060 for details."}],"companions":[]}
{"id":1312,"category":"Dependency Injection","question":"Why can't I instantiate a program or application with a constructor call in EK9?","url":"https://ek9.io/qa/QA1312.html","alternatePhrasings":["What triggers E05200 INCOMPATIBLE_GENUS_CONSTRUCTOR?","Why does 'app <- MyApplication()' fail to compile in EK9?","How do I start a program and its application without calling 'new'?"],"answer":"Programs and applications are not ordinary objects, so they cannot be created with a constructor call. Writing 'app <- MyApplication()' or 'prog <- MyProgram()' raises E05200 (INCOMPATIBLE_GENUS_CONSTRUCTOR): the PROGRAM, GENERAL_APPLICATION and SERVICE_APPLICATION genus types reject local constructor use.\n\nA program is the operating-system entry point; the runtime starts it. An application is the dependency-injection blueprint; it is brought to life by a program that declares 'with application of AppName'. You never construct either by hand.\n\nThe correct pattern is to define the application with its 'register' statements, then bind a program to it with 'with application of', and let injection ('!') resolve the registered components. No constructor call is needed or allowed for the program or the application itself.\n\nSee Q231 for program-application linking. See Q672 for the 'with application of' requirement.","ek9Example":"defines module qa.di.program.app.construction\n\n  defines component\n\n    OrderService as abstract\n      process() as abstract\n        <- summary as String?\n\n      default operator ?\n\n    StandardOrderService is OrderService\n      override process()\n        <- summary as String: \"order processed\"\n\n      default operator ?\n\n  defines application\n\n    //The application is the dependency-injection blueprint.\n    //It is never constructed by hand; a program brings it to life.\n    OrderApp\n      register StandardOrderService() as OrderService\n\n  defines program\n\n    //CORRECT: bind the program to the application with 'with application of'.\n    //No constructor call is made on the program or the application.\n    RunDemo() with application of OrderApp\n      stdout <- Stdout()\n\n      service as OrderService!\n      stdout.println(service.process())","migrationContext":"Java Spring: you can technically 'new' a @Configuration or @SpringBootApplication class, and an ApplicationContext is built by SpringApplication.run() - misuse is caught (if at all) at runtime. C#/.NET: Program.cs is conventionally a static entry; nothing stops you constructing a Startup. Go: main() is the entry point and wiring is manual code. EK9: the compiler models program and application as distinct genus types and rejects any constructor call on them (E05200), so the entry point and DI blueprint can never be misused as plain objects.","keywords":["E05200","application","constructor","entry point","genus","injection","instantiate","program","register","with application of"],"primaryTopics":["program and application genus","E05200","with application of binding"],"typicalErrors":[{"error":"E05200","correct":"    RunDemo() with application of OrderApp\n      stdout <- Stdout()\n","incorrect":"RunDemo()\n      app <- OrderApp()\n      service as OrderService!\n      service.process()","explanation":"An application (and a program) has a special genus and cannot be created with a constructor call, so 'app <- OrderApp()' triggers E05200 INCOMPATIBLE_GENUS_CONSTRUCTOR. The application is a dependency-injection blueprint, not an object you build: bind the program to it with 'with application of OrderApp' and let '!' inject the registered components. See ek9 -h E05200 for details."}],"companions":[]}
{"id":1313,"category":"Dependency Injection","question":"Why does EK9 reject an application that has no register statements?","url":"https://ek9.io/qa/QA1313.html","alternatePhrasings":["What triggers E07160 implementation must be provided on an application?","Why can't I declare an empty application block in EK9?","How do I fix 'definition of application: implementation must be provided'?"],"answer":"An application is a dependency-injection registry: its whole purpose is to bind concrete components to the abstract types that programs inject. An empty application body has no purpose and is almost always an incomplete definition, so EK9 raises E07160 (implementation must be provided). Unlike a method, an application cannot be marked 'as abstract' - it must contain at least one register statement (or a block statement). The fix is to add the register lines that wire your components.\n\n  defines application\n    GreetApp\n      register ConsoleGreeter() as Greeter\n\nSee Q1113 for wiring several components. See Q231 for linking a program to an application.","ek9Example":"defines module qa.di.application.body\n\n  defines component\n\n    Greeter as abstract\n      greet() as abstract\n        -> name as String\n        <- message as String?\n\n      default operator ?\n\n    ConsoleGreeter is Greeter\n      override greet()\n        -> name as String\n        <- message as String: `Hello, ${name}!`\n\n      default operator ?\n\n  defines application\n\n    //CORRECT: the application body provides at least one register statement,\n    //so it is a meaningful DI registry (not an empty body -> E07160).\n    GreetApp\n      register ConsoleGreeter() as Greeter\n\n  defines program\n\n    ApplicationBodyDemo() with application of GreetApp\n      stdout <- Stdout()\n      greeter as Greeter!\n      stdout.println(greeter.greet(\"World\"))","migrationContext":"Spring: an empty @Configuration class compiles silently and fails only at runtime when a bean is missing. Guice: an empty AbstractModule.configure() compiles and defers the error to injector creation. .NET: an empty service-collection setup compiles and surfaces missing-service errors at first resolve. EK9: an empty application is a compile-time error (E07160) - the registry must declare its bindings up front.","keywords":["DI","E07160","application","body","empty","implementation","register","registry","wiring"],"primaryTopics":["defines application","E07160","application body"],"typicalErrors":[{"error":"E07160","correct":"  defines application\n\n    //CORRECT: the application body provides at least one register statement,","incorrect":"  defines application\n    GreetApp","explanation":"An application with no register or block statements has an empty body. An application cannot be abstract and a registry with no bindings is meaningless, so EK9 raises E07160. Add at least one register statement that binds a concrete component to its abstract type. See ek9 -h E07160 for details."}],"companions":[]}
{"id":1314,"category":"Override Mechanics","question":"Why does 'default operator' in a derived class fail when the base class has no operators?","url":"https://ek9.io/qa/QA1314.html","alternatePhrasings":["What triggers E07190 default operator missing in super?","Why can't I use default operator when my parent class defines no operators?","How do I fix 'default of operators requires super to have appropriate operator'?"],"answer":"A 'default operator' in a derived class generates operators that call the SUPER class's matching operator first, then combine with the derived fields. If the base class has no operators (no 'default operator' and no manual operators), there is nothing for the derived default to chain to, so EK9 raises E07190 - one missing operator at a time (e.g. <=>, ==, $, #?, ?).\n\nFix - choose one of two approaches:\n\nAPPROACH 1 (recommended): add 'default operator' to the base class so the super has operators to call. Then 'default operator' in the derived class chains cleanly.\n  defines class\n    Shape as open\n      name as String: String()\n      default operator    // base now has operators\n    Circle extends Shape\n      radius as Float: 0.0\n      default operator    // chains to super's operators\n\nAPPROACH 2: implement the operators manually in the derived class instead of using 'default operator', writing each operator (?, $, <=>, etc.) explicitly.\n\nNote that an aggregate with fields also needs 'override operator ?' or 'default operator ?' to define set/unset semantics, so adding 'default operator' to the base fixes both concerns at once.\n\nSee Q1069 for mixing default operator with a custom override. See Q577 for override operator basics.","ek9Example":"defines module qa.overridemechanics.defaultsuperoperator\n\n  defines class\n\n    //THE FIX: the base class defines 'default operator' so the super has\n    //operators (<=>, ==, $, #?, ?) for the derived default to chain to.\n    Shape as open\n      name as String: String()\n\n      Shape()\n        -> name as String\n        this.name :=: name\n\n      default operator\n\n    //The derived 'default operator' generates operators that call super's\n    //matching operator first, then combine with the derived fields.\n    Circle extends Shape\n      radius as Float: 0.0\n\n      Circle()\n        ->\n          name as String\n          radius as Float\n        super(name)\n        this.radius :=: radius\n\n      default operator\n\n  defines program\n\n    DefaultSuperOperatorDemo()\n      stdout <- Stdout()\n\n      c1 <- Circle(\"small\", 1.0)\n      c2 <- Circle(\"large\", 5.0)\n\n      stdout.println(`Circle set: ${c1?}`)\n      stdout.println(`Circle: ${c1}`)\n      stdout.println(`Equal: ${c1 == Circle(\"small\", 1.0)}`)\n      stdout.println(`Compare: ${c1 <=> c2}`)","migrationContext":"Java: equals/hashCode/toString are inherited from Object, so a subclass's generated/IDE versions always have a super to call - no compile-time check ties them together. Kotlin data classes do not support inheritance of generated equals/hashCode at all (data classes are effectively final). EK9: 'default operator' explicitly chains to the super's operator and the compiler enforces (E07190) that the super actually defines the operator being defaulted, rather than silently producing inconsistent behaviour.","keywords":["E07190","base","default","extends","inherit","operator","override","super"],"primaryTopics":["default operator requires super operator","E07190","operator inheritance"],"typicalErrors":[{"error":"E07190","correct":"    Shape as open\n      name as String: String()\n\n      Shape()\n        -> name as String\n        this.name :=: name\n","incorrect":"Shape as open\n  name as String: String()\n  // no operators defined\n\nCircle extends Shape\n  radius as Float: 0.0\n  default operator","explanation":"A derived 'default operator' generates operators that call the super's matching operator first. When the base class (Shape) defines no operators, there is nothing for the derived default to chain to, so E07190 is raised once per missing operator (<=>, ==, $, #?, ?). Add 'default operator' to the base class so the super has operators to call, or implement the operators manually in the derived class instead of defaulting. See ek9 -h E07190 for details."}],"companions":[]}
{"id":1315,"category":"Operators and Expressions","question":"Why does 'default operator' fail when a property's type has no operators?","url":"https://ek9.io/qa/QA1315.html","alternatePhrasings":["What triggers E07200 PROPERTY_TYPE_MISSING_OPERATOR?","Why does my class with 'default operator' say a field type lacks an operator?","How do I use default operator when a property is a custom class?"],"answer":"'default operator' generates field-by-field implementations by calling the SAME operator on each property's type. So if a class uses 'default operator', EVERY property type must support the operators being generated (==, <>, <=>, $, #?, ? and so on). When a property's type is a custom class or record that defines no operators, the generated code has nothing to call and compilation fails with E07200.\n\nTwo fixes:\n1. Give the property's type its own operators - the simplest is to add 'default operator' to that type too, so it gains the full field-by-field set.\n2. Or implement the outer operators manually instead of using 'default', e.g. 'override operator ? as pure' returning a hand-written expression.\n\nBuilt-in types (Integer, Float, String, Boolean, Date, Time, DateTime, Duration, Money) already have all operators, so properties of those types never trigger E07200.\n\nSee Q949 for the full default operator rules. See Q245 for implementing operators by hand.","ek9Example":"defines module qa.operators.propertytypeoperator\n\n  defines class\n\n    <?-\n      The property TYPE must itself support the operators that 'default'\n      generates. Coordinate gets 'default operator' so it gains the full\n      field-by-field set (==, <>, <=>, $, #?, ?), satisfying Place below.\n    -?>\n    Coordinate\n      x <- Integer()\n      y <- Integer()\n\n      Coordinate() as pure\n        ->\n          x as Integer\n          y as Integer\n        this.x :=: x\n        this.y :=: y\n\n      default operator\n\n    <?-\n      Place uses 'default operator'. Because its property 'where' is a\n      Coordinate that now supports all operators, the generated\n      field-by-field code compiles cleanly - no E07200.\n    -?>\n    Place\n      name <- String()\n      where <- Coordinate()\n\n      Place() as pure\n        ->\n          name as String\n          where as Coordinate\n        this.name :=: name\n        this.where :=: where\n\n      default operator\n\n  defines program\n\n    PropertyTypeOperatorDemo()\n      stdout <- Stdout()\n\n      origin <- Coordinate(0, 0)\n      target <- Coordinate(3, 4)\n\n      home <- Place(\"home\", origin)\n      away <- Place(\"away\", target)\n\n      stdout.println(`home == away: ${home == away}`)\n      stdout.println(`home: ${home}`)\n      stdout.println(`home set: ${home?}`)","migrationContext":"Java: IDE-generated equals/hashCode silently compiles even if a field type has no usable equals (falling back to Object identity) - a latent bug. Kotlin data class / Rust derive(Eq, Hash) / Python dataclass all require contained types to themselves support the derived behaviour, but the failure modes differ (Rust errors at derive, Java does not). EK9 makes the requirement explicit at compile time: every property type must provide the operator that 'default' generates, or E07200 is raised.","keywords":["E07200","E07235","auto","default","field","generate","missing","operator","property","type"],"primaryTopics":["default operator","E07200","property type operators"],"typicalErrors":[{"error":"E07200","correct":"    Coordinate\n      x <- Integer()\n      y <- Integer()\n\n      Coordinate() as pure\n        ->\n          x as Integer\n          y as Integer","incorrect":"Coordinate\n  x <- Integer()\n  y <- Integer()\n  // no operators\n\nPlace\n  where <- Coordinate()\n  default operator","explanation":"'default operator' on Place generates field-by-field code that calls operators (such as $ and ?) on the type of every property. The property 'where' is a Coordinate, but Coordinate defines no operators, so the generated implementation has nothing to call and E07200 is raised for the missing operator. The fix is to give Coordinate its own operators - adding 'default operator' to Coordinate is the simplest, or implement Place's operators manually instead of using 'default'. See ek9 -h E07200 for details."}],"companions":[]}
{"id":1316,"category":"Web Services","question":"Why can't a helper method in an EK9 service be marked 'protected'?","url":"https://ek9.io/qa/QA1316.html","alternatePhrasings":["What triggers E07240 METHOD_MODIFIER_PROTECTED_IN_SERVICE?","How do I hide an internal helper method inside an EK9 service?","Why does EK9 reject 'protected' on a non-web service method?"],"answer":"A non-web method inside a 'defines service' construct cannot be marked 'protected'. Services are entry points, not extensible classes, so the 'protected' concept (granting access to subclasses) has no meaning for them and EK9 raises E07240. Use 'private' for an internal helper that should not be exposed as a route, or leave the method public (the default) if it is a web endpoint. Web methods carry an HTTP mapping such as 'as GET for :/path'; plain helper methods do not and so must be 'private' or public, never 'protected'.\n\nSee Q199 for REST GET endpoints. See Q201 for HTTP responses.","ek9Example":"defines module qa.web.serviceprotected\n\n  defines service\n\n    Greeting :/greeting open\n\n      hello() as GET for :/hello\n        <- response as HTTPResponse?\n\n        message <- buildMessage()\n        response: (message) with trait HTTPResponse\n          override content()\n            <- rtn as String: message\n          override status() as pure\n            <- rtn as Integer: 200\n          override contentType() as pure\n            <- rtn as String: \"text/plain\"\n          override cacheControl() as pure\n            <- rtn as String: \"no-store\"\n          override contentLanguage() as pure\n            <- rtn as String: \"en\"\n          default operator ?\n\n      //CORRECT: an internal helper uses 'private', never 'protected'.\n      //'protected' on a non-web service method raises E07240.\n      private buildMessage()\n        <- rtn as String: \"Hello from the EK9 service\"\n\n  defines application\n\n    GreetingApp\n      register Greeting()\n\n  defines program\n\n    ServiceProtectedDemo()\n      stdout <- Stdout()\n\n      stdout.println(\"Service registered with application\")\n      stdout.println(\"GET /greeting/hello returns a greeting\")\n      stdout.println(\"Internal helper is private, not protected\")","migrationContext":"Java (JAX-RS/Spring): a resource class is a normal class, so 'protected' helper methods compile fine and are governed only by inheritance. Kotlin/C#: same - controllers are ordinary classes with full visibility ranges. EK9: services are dedicated entry-point constructs, not inheritable classes, so 'protected' is rejected at compile time (E07240); internal helpers use 'private' instead.","keywords":["E07240","helper","method","modifier","private","protected","rest","service","visibility","web"],"primaryTopics":["service visibility","E07240","protected in service"],"typicalErrors":[{"error":"E07240","correct":"      private buildMessage()\n        <- rtn as String: \"Hello from the EK9 service\"","incorrect":"protected buildMessage()\n  <- rtn as String: \"internal\"","explanation":"Marking a non-web service method 'protected' triggers E07240, because a service is an entry point - not an extensible class - so subclass-oriented 'protected' visibility is meaningless. Use 'private' for an internal helper (or leave it public). See ek9 -h E07240 for details."}],"companions":[]}
{"id":1317,"category":"Classes and OOP","question":"Why can't I mark a method 'protected' in a closed class?","url":"https://ek9.io/qa/QA1317.html","alternatePhrasings":["What triggers E07260 METHOD_MODIFIER_PROTECTED_IN_CLOSED_CLASS?","Why does EK9 reject 'protected' on a method in a non-open class?","When is the 'protected' access modifier valid on a class method?"],"answer":"The 'protected' access modifier only makes sense for inheritance: it grants access to subclasses while hiding the member from outside code. A class that is closed (the EK9 default) can never have subclasses, so 'protected' would be meaningless there. EK9 rejects it at compile time with E07260.\n\nTHE FIX\nThere are two correct paths depending on intent:\n  1. If you DO intend the class to be extended, mark the class 'as open'. Then 'protected' is valid because subclasses exist to use it.\n  2. If no inheritance is intended, use 'private' instead. A private method is fully encapsulated and needs no open class.\n\nWHY EK9 IS STRICT\nIn Java you can write 'protected' in a final class and it silently behaves like package-private nonsense; the modifier is dead. EK9 eliminates that dead, misleading code: 'protected' must match a real inheritance relationship or it does not compile.\n\nSee Q102 for 'as open'. See Q101 for closed-by-default. See Q573 for override access modifier rules.","ek9Example":"defines module qa.oop.protected.open\n\n  defines class\n\n    //CORRECT: the class is 'as open', so 'protected' is meaningful -\n    //subclasses can call validate() while outside code cannot.\n    Account as open\n      balance as Float: 0.0\n\n      Account()\n        -> opening as Float\n        this.balance: opening\n\n      //Protected: visible to subclasses, hidden from the outside world.\n      protected validate() as pure\n        <- rtn as Boolean: balance >= 0.0\n\n      describe()\n        <- rtn as String: `Account balance ${balance}`\n\n      default operator ?\n\n    //Subclass exercises the protected method - the reason 'protected' exists.\n    SavingsAccount extends Account\n      SavingsAccount()\n        -> opening as Float\n        super(opening)\n\n      isHealthy() as pure\n        <- rtn as Boolean: validate()\n\n      default operator ?\n\n  defines program\n\n    ProtectedRequiresOpenDemo()\n      stdout <- Stdout()\n\n      savings <- SavingsAccount(100.0)\n      stdout.println(savings.describe())\n      stdout.println(`Healthy: ${savings.isHealthy()}`)","migrationContext":"Java: 'protected' compiles even on a 'final' class, where it can never be exercised by a subclass - the modifier is silently dead code. Kotlin: 'protected' is allowed on a final class with only a warning at most. C#: 'protected' on a sealed class is permitted but useless. EK9: compile-time error E07260 - 'protected' requires the class to be 'open' (or abstract, which is implicitly open), otherwise use 'private'.","keywords":["E07260","access","class","closed","inheritance","method","modifier","open","private","protected","subclass","visibility"],"primaryTopics":["protected modifier","E07260","open class","access modifiers"],"typicalErrors":[{"error":"E07260","correct":"    Account as open\n      balance as Float: 0.0\n","incorrect":"Account\n  protected validate() as pure\n    <- rtn as Boolean: true","explanation":"'protected' grants access to subclasses, but a closed class (the EK9 default) can never be extended, so the modifier is meaningless and triggers E07260. Either mark the class 'as open' so subclasses exist to use the protected method, or use 'private' if no inheritance is intended. See ek9 -h E07260 for details."}],"companions":[]}
{"id":1318,"category":"Control Flow","question":"Why does EK9 say a return is not possible when all branches throw?","url":"https://ek9.io/qa/QA1318.html","alternatePhrasings":["What triggers E07380 in EK9?","Why can't my function return a value if every if/else branch throws?","How do I fix 'return not possible, as instructions only result in an Exception'?"],"answer":"EK9 performs flow analysis on functions and methods that declare a return variable (<- rtn). If every path through the body ends in throw, the declared return can never be assigned or reached, so the compiler raises E07380 ('return not possible, as instructions only result in an Exception'). This usually means a fallible function was written so that BOTH the failure branch and the success branch throw - leaving no normal path that produces a value.\n\nThe fix is to keep at least one non-throwing path that assigns the return variable. Throw only for the genuinely exceptional case (typically guarded by a condition), then fall through to assign and return the normal result. A conditional throw is fine; an unconditional throw on every branch is not.\n\nSee Q933 for unreachable code after throw (E07370). See Q139 for try/catch versus Result for fallible work.","ek9Example":"defines module qa.controlflow.unreachable.return\n\n  defines function\n\n    //FIX: only the failure case throws. The normal path falls through,\n    //assigns the return variable, and is reachable - so E07380 is avoided.\n    acceptAmount() as pure\n      -> amount as Integer\n      <-\n        rtn as String?\n      if amount <= 0\n        throw Exception(`bad amount: ${amount}`)\n      rtn: `accepted ${amount}`\n\n  defines program\n\n    UnreachableReturnDemo()\n      stdout <- Stdout()\n      stderr <- Stderr()\n\n      //Normal path: a value is produced and returned.\n      stdout.println(acceptAmount(42))\n\n      //Failure path: the conditional throw fires and is caught.\n      try\n        stdout.println(acceptAmount(-1))\n      catch\n        -> ex as Exception\n        stderr.println(`Caught: ${ex}`)","migrationContext":"Java: a method whose every branch throws compiles fine - the missing return is simply never required, and dead-code-after-throw is only caught when literally unreachable statements follow. Kotlin/Scala: such a method infers the bottom type 'Nothing' and is accepted silently. Go: returning is optional after a panic, no diagnostic. EK9: because a declared return variable is a contract, a body that can only throw makes that contract unsatisfiable, so the compiler rejects it at compile time (E07380) rather than shipping a function that promises a value it can never deliver.","keywords":["E07380","all paths","analysis","exception","flow","return","throw","unreachable"],"primaryTopics":["unreachable return","all paths throw","E07380"],"typicalErrors":[{"error":"E07380","correct":"if amount <= 0\n        throw Exception(`bad amount: ${amount}`)\n      rtn: `accepted ${amount}`","incorrect":"if amount <= 0\n  throw Exception(`bad amount: ${amount}`)\nelse\n  throw Exception(\"unexpected\")\nrtn: `accepted ${amount}`","explanation":"When both the if and the else branch throw, every path through the function ends in an Exception, so the declared return variable can never be reached or assigned - the function can only ever throw. EK9 raises E07380 because the declared return is then a promise the body cannot keep. Remove the unconditional throw on the success branch (throw only for the failure case) so a normal path remains that assigns the return. See ek9 -h E07380 for details."}],"companions":[]}
{"id":1319,"category":"Functions and Methods","question":"Why does EK9 reject an operator that takes a parameter but has no return declaration?","url":"https://ek9.io/qa/QA1319.html","alternatePhrasings":["What triggers E07400 RETURNING_MISSING on an operator?","Why must my '+' operator declare a '<- rtn' return value?","How do I fix 'returning variable and type missing' in EK9?"],"answer":"Operators with value semantics (such as +, -, *, /, <=>, $, #?) MUST produce a value, so EK9 requires a return declaration in their signature. Omitting the '<- rtn as Type: ...' line raises E07400 (RETURNING_MISSING): the operator promises a result but never declares one, so any expression using it would have nothing to evaluate to.\n\nEK9 has no return keyword - the return value is a named variable declared with '<-' in the signature. The compiler then enforces that all paths give it a value. To fix E07400, add the return declaration:\n\n  operator + as pure\n    -> other as Vector\n    <- rtn as Vector: Vector(x + other.x)\n\nNote this is different from mutating operators like ++ and -- which must NOT declare a return (that would raise RETURN_VALUE_NOT_SUPPORTED).\n\nSee Q1001 for the general '<- rtn' return mechanism.","ek9Example":"defines module qa.functionsandmethods.operatorreturn\n\n  defines class\n\n    Vector\n      x as Integer: Integer()\n\n      Vector() as pure\n        -> initial as Integer\n        x :=? initial\n\n      //CORRECT: a value-producing operator declares its return with '<- rtn'.\n      //Omitting this line would raise E07400 RETURNING_MISSING.\n      operator + as pure\n        -> other as Vector\n        <- rtn as Vector: Vector(x + other.x)\n\n      operator $ as pure\n        <- rtn as String: `Vector(${x})`\n\n      override operator ? as pure\n        <- rtn as Boolean: x?\n\n  defines program\n\n    OperatorReturnDemo()\n      stdout <- Stdout()\n\n      a <- Vector(3)\n      b <- Vector(4)\n      sum <- a + b\n      stdout.println(`Sum is ${sum}`)","migrationContext":"Java/Kotlin: operator overloads are just methods whose return type is part of the signature; forgetting to return compiles only if the declared return type is void, otherwise it is a normal type error. C++: an operator+ with a non-void return that never returns is undefined behaviour, often only a warning. EK9: the missing return is a hard compile-time error (E07400) at type-definition time, because the return value is a declared variable the compiler can see is absent.","keywords":["E07400","RETURNING_MISSING","declaration","missing","operator","return","rtn","value"],"primaryTopics":["operator return declaration","E07400","missing return"],"typicalErrors":[{"error":"E07400","correct":"operator + as pure\n        -> other as Vector\n        <- rtn as Vector: Vector(x + other.x)","incorrect":"operator + as pure\n  -> other as Vector","explanation":"A value-producing operator like '+' must declare what it returns, but this signature has only an incoming parameter and no '<- rtn as Type' line, so EK9 raises E07400 - the operator promises a result it never produces. Add the return declaration '<- rtn as Vector: ...' so all paths supply a value. (Mutating operators such as ++ and -- are the opposite: they must NOT declare a return.) See ek9 -h E07400 for details."}],"companions":[]}
{"id":1320,"category":"Operators and Expressions","question":"Why can't I declare 'operator !=' on my EK9 type?","url":"https://ek9.io/qa/QA1320.html","alternatePhrasings":["What triggers E07640 BAD_NOT_EQUAL_OPERATOR?","How do I implement not-equal on a custom EK9 class?","Why does EK9 reject '!=' as an operator name?"],"answer":"EK9's not-equal operator is '<>' (mathematical notation), not the C-family '!='. When you declare an operator on a type you must name it '<>'. Declaring 'operator != as pure' triggers E07640 (BAD_NOT_EQUAL_OPERATOR) because '!=' is not a valid EK9 operator symbol for a declaration.\n\nReplace 'operator !=' with 'operator <>'. The operator must be pure, take exactly one argument of the same type, and return Boolean. EK9 chose '<>' because it is the mathematical not-equal form (the same as Pascal, SQL and BASIC) and composes naturally with '<' and '>'.\n\nSee Q238 for the complete fixed operator set. See Q239 for comparison and ordering operators.","ek9Example":"defines module qa.operators.notequal\n\n  defines class\n\n    Score\n      points <- Integer()\n\n      Score() as pure\n        -> points as Integer\n        this.points :=: points\n\n      operator == as pure\n        -> other as Score\n        <- rtn as Boolean: points == other.points\n\n      //THE FIX: EK9's not-equal operator is '<>', NOT '!='.\n      //Declaring 'operator !=' would trigger E07640.\n      operator <> as pure\n        -> other as Score\n        <- rtn as Boolean: points <> other.points\n\n      operator $ as pure\n        <- rtn as String: $points\n\n      override operator ? as pure\n        <- rtn as Boolean: points?\n\n  defines program\n\n    NotEqualOperatorDemo()\n      stdout <- Stdout()\n\n      a <- Score(10)\n      b <- Score(20)\n\n      stdout.println(`Equal: ${a == b}`)\n      stdout.println(`Not equal: ${a <> b}`)","migrationContext":"Java/C++/Python/Rust/JavaScript: not-equal is '!=' and (where overloading exists) you implement it under that symbol or as a negated equals. EK9: the only not-equal operator symbol is '<>', and the compiler rejects a '!=' declaration at compile time (E07640, phase EXPLICIT_TYPE_SYMBOL_DEFINITION) rather than silently accepting an alternate spelling.","keywords":["!=","<>","E07640","comparison","declaration","equal","inequality","neq","not","operator"],"primaryTopics":["not-equal operator","E07640","operator declaration"],"typicalErrors":[{"error":"E07640","correct":"operator <> as pure\n        -> other as Score\n        <- rtn as Boolean: points <> other.points","incorrect":"operator != as pure\n  -> other as Score\n  <- rtn as Boolean: points <> other.points","explanation":"EK9 uses the mathematical not-equal symbol '<>', not the C-family '!='. Declaring 'operator !=' is not a valid EK9 operator name and triggers E07640. Rename the declaration to 'operator <>' (pure, one argument, returns Boolean). See ek9 -h E07640 for details."}],"companions":[]}
{"id":1321,"category":"Operators and Expressions","question":"Why can't I declare 'operator not' on a type in EK9?","url":"https://ek9.io/qa/QA1321.html","alternatePhrasings":["What triggers E07650 when defining a logical NOT operator?","How do I implement boolean negation on a custom type in EK9?","Why does EK9 reject 'operator not' and want '~' instead?"],"answer":"EK9 has no 'not' operator and no '!' logical-NOT. The single logical-NOT (and bitwise complement) operator is '~'. When you declare 'operator not' on a class, record, or trait the compiler raises E07650 (BAD_NOT_OPERATOR) and tells you to use '~'. The same applies at call sites: write '~value', never '!value' or 'not value' for negation. ('not' IS a valid keyword, but only as the prefix of 'not contains' / 'not in' membership tests, not as a standalone logical-NOT.)\n\nDefine the operator with the '~' symbol; on a custom type it must be pure, take no arguments, and return the SAME type (so 'Switch' returns a negated 'Switch'). On the built-in Boolean, '~' yields the negated Boolean directly.\n\nSee Q244 for boolean and bitwise operators. See Q903 for operators that do NOT exist in EK9.","ek9Example":"defines module qa.operators.notistilde\n\n  defines class\n\n    //THE FIX: the logical-NOT operator is declared with '~', not 'not' and not '!'.\n    //It is pure, takes no arguments, and returns a Boolean here.\n    Switch\n      active as Boolean: Boolean()\n\n      Switch() as pure\n        -> initial as Boolean\n        active :=? initial\n\n      operator ~ as pure\n        <- rtn as Switch: Switch(~ active)\n\n      operator $ as pure\n        <- rtn as String: $ active\n\n      override operator ? as pure\n        <- rtn as Boolean: active?\n\n  defines program\n\n    NotOperatorDemo()\n      stdout <- Stdout()\n\n      on <- Switch(true)\n      off <- ~ on\n\n      stdout.println(`Switch on: ${on}`)\n      stdout.println(`Negated:   ${off}`)","migrationContext":"Java/C/C++/JavaScript/Rust: '!' is logical NOT. Python: 'not' keyword is logical NOT. Kotlin: '!' plus an overloadable 'not()' operator function. EK9: neither '!' nor 'not' is a logical-NOT operator; '~' is the single negation operator, enforced at compile time both when DEFINING the operator (E07650) and at call sites.","keywords":["E07650","boolean","complement","logical","negation","not","operator","tilde"],"primaryTopics":["logical NOT operator","E07650","tilde negation"],"typicalErrors":[{"error":"E07650","correct":"operator ~ as pure\n        <- rtn as Switch: Switch(~ active)","incorrect":"operator not as pure\n  <- rtn as Switch: Switch(~ active)","explanation":"EK9 has no 'not' operator and no '!' logical-NOT; declaring 'operator not' on a type raises E07650. Rename the operator to '~', which is EK9's single logical-NOT and bitwise-complement operator. At call sites use '~value' too, never '!value'. See ek9 -h E07650 for details."}],"companions":[]}
{"id":1322,"category":"Web Services","question":"Why does EK9 reject some operators inside a service definition?","url":"https://ek9.io/qa/QA1322.html","alternatePhrasings":["What triggers E07670 unsupported service operator?","Which operators are allowed inside an EK9 service?","Why can't I use operator * in an EK9 REST service?"],"answer":"An EK9 service maps operators to HTTP verbs, so only the operators that have a clear CRUD meaning are permitted. The supported set is +, +=, -, -=, :^:, :~: and ?. Using any other operator (for example * or /) inside a service raises E07670 because there is no HTTP verb to map it to.\n\nThe semantic mapping is: += and + create (POST), -= and - remove (DELETE), :^: replaces (PUT), :~: merges (PATCH), and ? checks whether the service is set. Arithmetic operators like * have no place at the HTTP boundary.\n\nFix: keep the service surface limited to the supported operators, and move any computational behaviour (such as multiplication) into a plain helper class or function that the service handler calls.\n\nSee Q200 for CRUD operator mapping. See Q660 for path parameters with operators.","ek9Example":"defines module qa.web.unsupported.operator\n\n  defines service\n\n    //CORRECT: only the supported, HTTP-mappable operators appear here.\n    //Any arithmetic (such as multiplication) lives in a helper, not the service.\n    Items :/items open\n\n      // GET /items — list all items\n      listAll() :/\n        <- response as HTTPResponse: () with trait HTTPResponse\n          override content()\n            <- rtn as String: `[\"item1\", \"item2\"]`\n          override status() as pure\n            <- rtn as Integer: 200\n          override contentType() as pure\n            <- rtn as String: \"application/json\"\n          override cacheControl() as pure\n            <- rtn as String: \"no-cache\"\n          override contentLanguage() as pure\n            <- rtn as String: \"en\"\n          default operator ?\n\n      // POST /items — add new item (supported operator, maps to create)\n      operator += :/\n        -> content as String :=: CONTENT\n        <- response as HTTPResponse: () with trait HTTPResponse\n          override content()\n            <- rtn as String: `{\"created\": true}`\n          override status() as pure\n            <- rtn as Integer: 201\n          override contentType() as pure\n            <- rtn as String: \"application/json\"\n          override cacheControl() as pure\n            <- rtn as String: \"no-store\"\n          override contentLanguage() as pure\n            <- rtn as String: \"en\"\n          default operator ?\n\n  defines class\n\n    //Arithmetic belongs in a plain helper, never in the service surface.\n    Calculator\n      multiply() as pure\n        ->\n          a as Integer\n          b as Integer\n        <-\n          rtn as Integer: a * b\n\n  defines application\n\n    ItemsApp\n      register Items()\n\n  defines program\n\n    UnsupportedServiceOperatorDemo()\n      stdout <- Stdout()\n      calc <- Calculator()\n      stdout.println(\"Service uses only supported operators: +, +=, -, -=, :^:, :~:, ?\")\n      stdout.println(`Arithmetic stays in a helper: 6 * 7 = ${calc.multiply(6, 7)}`)","migrationContext":"Java: Spring/JAX-RS use method annotations (@GetMapping, @PostMapping); an arbitrary method is allowed and only meaningful at runtime. Python Flask/FastAPI: any function can be a route, no restriction on its name or shape. Go/Rust: handlers are ordinary functions wired to verbs by the router. EK9: the service surface is restricted at compile time to the operators that map to HTTP verbs (+, +=, -, -=, :^:, :~:, ?); anything else is rejected with E07670 so business arithmetic never leaks into the transport layer.","keywords":["E07670","crud","http","operator","rest","service","unsupported","verb","webservice"],"primaryTopics":["unsupported service operator","E07670","service operator mapping"],"typicalErrors":[{"error":"E07670","correct":"      operator += :/\n        -> content as String :=: CONTENT\n        <- response as HTTPResponse: () with trait HTTPResponse","incorrect":"operator * :/{id}\n  -> id as String\n  <- response as HTTPResponse: ...","explanation":"The * operator has no HTTP-verb meaning, so it cannot appear inside a service and triggers E07670. Only +, +=, -, -=, :^:, :~: and ? are supported because each maps to a CRUD HTTP verb. Use a supported operator (for example += for POST) for the endpoint and move any arithmetic into a helper class. See ek9 -h E07670 for details."}],"companions":[]}
{"id":1323,"category":"Web Services","question":"Why does EK9 reject an HTTP access marker (:=: PATH) on a service operation's returning parameter?","url":"https://ek9.io/qa/QA1323.html","alternatePhrasings":["What triggers E07690 HTTP access verb not supported in this context?","Why can't I put :=: PATH or :=: HEADER on a service return value in EK9?","Where are HTTP access markers (PATH, HEADER, QUERY, CONTENT) allowed in an EK9 service?"],"answer":"An HTTP access marker (:=: PATH, :=: HEADER, :=: QUERY, :=: REQUEST, :=: CONTENT, :=: CONTEXT) tells EK9 where to source an INCOMING value from the HTTP request. It is therefore only valid on the INCOMING parameters of a service operation. Putting one on a returning parameter, an aggregate property, or a plain block variable makes no sense - there is nothing to bind from the request - so EK9 raises E07690 (HTTP access verb not supported in this context) at compile time.\n\nThe fix is to bind request data only on the incoming parameter list and let the returning parameter be an ordinary HTTPResponse. The HTTP verb of the operation (GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS) describes the operation; the access marker describes where each incoming argument comes from.\n\nSee Q658 for path-parameter binding. See Q202 for request parameter binding.","ek9Example":"defines module qa.webdeep.httpaccessmarker\n\n  defines constant\n\n    JSON_CONTENT_TYPE <- \"application/json\"\n    ENGLISH_LANGUAGE <- \"en\"\n\n  defines service\n\n    <?-\n      An HTTP access marker (:=: PATH here) is valid ONLY on an incoming\n      parameter, where it binds a value from the request. The returning\n      parameter is a plain HTTPResponse with NO access marker - adding one\n      there would trigger E07690.\n    -?>\n    Items :/items open\n\n      //CORRECT: :=: PATH on the incoming 'id' only; the return is a plain HTTPResponse.\n      byId() as GET for :/{id}\n        ->\n          id as String :=: PATH\n        <-\n          response as HTTPResponse: (capturedId: id) with trait HTTPResponse\n            override content()\n              <- rtn as String: `{\"id\": \"${capturedId}\"}`\n            override status() as pure\n              <- rtn as Integer: 200\n            override contentType() as pure\n              <- rtn as String: JSON_CONTENT_TYPE\n            override cacheControl() as pure\n              <- rtn as String: \"no-cache\"\n            override contentLanguage() as pure\n              <- rtn as String: ENGLISH_LANGUAGE\n            default operator ?\n\n  defines application\n\n    ItemsApp\n      register Items()\n\n  defines program\n\n    HttpAccessMarkerDemo()\n      stdout <- Stdout()\n      stdout.println(\"Service registered (no E07690):\")\n      stdout.println(\"  GET /items/{id} binds {id} via :=: PATH on the incoming parameter\")\n      stdout.println(\"  the returning HTTPResponse carries no access marker\")","migrationContext":"Java Spring: @PathVariable / @RequestHeader can only annotate handler parameters; placing them elsewhere is simply ignored or a runtime wiring error. JAX-RS @PathParam on a return is meaningless and silently dropped. EK9 instead rejects a misplaced access marker at compile time with E07690, so request-binding can only ever describe an incoming value.","keywords":["CONTENT","E07690","HEADER","PATH","QUERY","access","binding","http","marker","returning","service","webservice"],"primaryTopics":["HTTP access marker placement","E07690","service request binding"],"typicalErrors":[{"error":"E07690","correct":"      byId() as GET for :/{id}\n        ->\n          id as String :=: PATH\n        <-\n          response as HTTPResponse: (capturedId: id) with trait HTTPResponse","incorrect":"byId() as GET for :/{id}\n  ->\n    id as String :=: PATH\n  <-\n    response as HTTPResponse :=: PATH","explanation":"An HTTP access marker (:=: PATH) sources an INCOMING value from the request, so it is only valid on an incoming parameter. Placing it on the returning parameter (or a property/local) has nothing to bind and triggers E07690. Remove the marker from the return and keep it only on the incoming argument. See ek9 -h E07690 for details."}],"companions":[]}
{"id":1324,"category":"Web Services","question":"Why does a QUERY/HEADER/PATH service parameter need a name qualifier in EK9?","url":"https://ek9.io/qa/QA1324.html","alternatePhrasings":["What triggers E07710 SERVICE_HTTP_PARAM_NEEDS_QUALIFIER?","Why must I name the QUERY string for a service parameter?","How do I bind a query string parameter to a method argument in EK9?"],"answer":"An EK9 service parameter bound with QUERY, HEADER, or PATH must declare the wire name it binds to as a String qualifier. The method-parameter identifier is your local variable name; the qualifier string is the actual HTTP query key, header name, or path segment name. Without the qualifier the compiler cannot know which request value to extract, so it raises E07710.\n\nFix: add the qualifier string. For a query string ?q=...:\n  -> term as String :=: QUERY \"q\"\nFor an HTTP header:\n  -> token as String :=: HEADER \"Authorization\"\nFor a named path segment, the qualifier follows the :/{seg} variable in the URI.\n\nThe REQUEST qualifier is the exception - it binds the whole HTTPRequest and must NOT carry a name (a name there triggers E07720).\n\nSee Q945 for HTTP verbs and URI params. See Q658 for path parameter binding. See Q846 for valid service parameter types.","ek9Example":"defines module qa.webdeep.service.param.qualifier\n\n  defines constant\n\n    JSON_TYPE <- \"application/json\"\n\n  defines service\n\n    <?-\n      Service demonstrating named QUERY and HEADER parameter binding.\n      Each parameter declares the wire name it reads from, so the\n      compiler can extract the right request value (no E07710).\n    -?>\n    SearchService :/search open\n\n      // GET /search/results?q=... with an Authorization header\n      find() as GET for :/results\n        ->\n          term as String :=: QUERY \"q\"\n          token as String :=: HEADER \"Authorization\"\n        <- response as HTTPResponse: (capturedTerm: term, capturedToken: token) with trait of HTTPResponse\n          override content()\n            <- rtn as String: `{\"query\": \"${capturedTerm}\", \"authorized\": ${capturedToken?}}`\n          override status() as pure\n            <- rtn as Integer: 200\n          override contentType() as pure\n            <- rtn as String: JSON_TYPE\n          override cacheControl() as pure\n            <- rtn as String: \"no-cache\"\n          override contentLanguage() as pure\n            <- rtn as String: \"en\"\n          default operator ?\n\n  defines application\n\n    SearchApp\n      register SearchService()\n\n  defines program\n\n    ServiceParamQualifierDemo()\n      stdout <- Stdout()\n      stdout.println(\"Named service parameter binding:\")\n      stdout.println(\"  GET /search/results?q=... -> QUERY \\\"q\\\" binds to term\")\n      stdout.println(\"  Header Authorization      -> HEADER \\\"Authorization\\\" binds to token\")","migrationContext":"Java Spring: @RequestParam(\"q\"), @RequestHeader(\"Authorization\"), @PathVariable(\"id\") - the wire name is an annotation argument, omittable so it defaults to the Java parameter name (silent at compile time, surprises at runtime). Python Flask, Go gorilla/mux: the wire key is a plain string in code, never checked. EK9: the wire name is a mandatory part of the binding syntax, enforced at compile time (E07710), so a parameter can never silently bind to the wrong request value.","keywords":["E07710","HEADER","PATH","QUERY","REST","binding","http","name","parameter","qualifier","service"],"primaryTopics":["service parameter qualifier","E07710","SERVICE_HTTP_PARAM_NEEDS_QUALIFIER"],"typicalErrors":[{"error":"E07710","correct":"->\n          term as String :=: QUERY \"q\"","incorrect":"        -> term as String :=: QUERY","explanation":"A QUERY (or HEADER or PATH) binding must state the wire name it reads from. Without it, the compiler cannot tell which query key, header, or path segment to extract, so it raises E07710. Add the name qualifier - 'QUERY \"q\"' binds the ?q= query string to the 'term' parameter. (Only REQUEST takes no name.) See ek9 -h E07710 for details."}],"companions":[]}
{"id":1325,"category":"Web Services","question":"Why does EK9 reject a qualifier name on a REQUEST or CONTENT service parameter?","url":"https://ek9.io/qa/QA1325.html","alternatePhrasings":["What triggers E07720 SERVICE_HTTP_PARAM_QUALIFIER_NOT_ALLOWED?","Why can't I add a name after :=: REQUEST or :=: CONTENT?","Which HTTP service bindings take a qualifier name and which do not?"],"answer":"EK9 service parameters bind to one HTTP source via :=:. Only PATH, QUERY and HEADER bindings take an extra qualifier name (the URI segment, query key or header name) so the runtime knows which value to extract. REQUEST and CONTENT bind to the whole request object or the whole request body respectively, so there is nothing to name. Adding a quoted qualifier to REQUEST or CONTENT triggers E07720 (SERVICE_HTTP_PARAM_QUALIFIER_NOT_ALLOWED) at the EXPLICIT_TYPE_SYMBOL_DEFINITION phase.\n\nCORRECT\n  -> request as HTTPRequest :=: REQUEST\n  -> body as String :=: CONTENT\n\nINCORRECT\n  -> request as HTTPRequest :=: REQUEST \"x\"   // E07720\n  -> body as String :=: CONTENT \"payload\"      // E07720\n\nThe fix is simply to delete the qualifier name. Contrast with E07710 (missing qualifier), which fires when PATH, QUERY or HEADER lacks the name they DO require.\n\nSee Q658 for path parameter binding. See Q868 for service parameter types.","ek9Example":"defines module qa.webdeep.service.param.qualifier\n\n  defines constant\n\n    JSON_RESPONSE <- \"application/json\"\n\n  defines service\n\n    <?-\n      Service showing the bindings that do NOT take a qualifier name.\n      REQUEST binds the whole request, CONTENT binds the whole body.\n    -?>\n    EchoService :/echo open\n\n      // REQUEST binds the full HTTPRequest - no qualifier name allowed.\n      describe() as GET for :/details\n        -> request as HTTPRequest :=: REQUEST\n        <- response as HTTPResponse: (incoming: request.content()) with trait HTTPResponse\n          override content()\n            <- rtn as String: `{\"request\": \"${incoming}\"}`\n          override status() as pure\n            <- rtn as Integer: 200\n          override contentType() as pure\n            <- rtn as String: JSON_RESPONSE\n          override cacheControl() as pure\n            <- rtn as String: \"no-cache\"\n          override contentLanguage() as pure\n            <- rtn as String: \"en\"\n          default operator ?\n\n      // CONTENT binds the whole request body - no qualifier name allowed.\n      operator += :/\n        -> body as String :=: CONTENT\n        <- response as HTTPResponse: (received: body) with trait HTTPResponse\n          override content()\n            <- rtn as String: `{\"received\": \"${received}\"}`\n          override status() as pure\n            <- rtn as Integer: 201\n          override contentType() as pure\n            <- rtn as String: JSON_RESPONSE\n          override cacheControl() as pure\n            <- rtn as String: \"no-store\"\n          override contentLanguage() as pure\n            <- rtn as String: \"en\"\n          default operator ?\n\n  defines application\n\n    EchoApp\n      register EchoService()\n\n  defines program\n\n    QualifierDemo()\n      stdout <- Stdout()\n      stdout.println(\"No qualifier name: :=: REQUEST and :=: CONTENT\")\n      stdout.println(\"Qualifier name required: :=: PATH, :=: QUERY, :=: HEADER\")","migrationContext":"Java Spring: @RequestBody and the HttpServletRequest argument take no name, while @RequestParam/@PathVariable/@RequestHeader take a key string; mixing them up is silently accepted or fails at runtime. ASP.NET / Flask / Go behave similarly with no compile-time guard. EK9 enforces the distinction at compile time: REQUEST and CONTENT must have no qualifier, PATH/QUERY/HEADER must have one.","keywords":["CONTENT","E07720","HEADER","QUERY","REQUEST","binding","http","parameter","qualifier","service"],"primaryTopics":["service parameter qualifier","E07720","REQUEST and CONTENT binding"],"typicalErrors":[{"error":"E07720","correct":"        -> request as HTTPRequest :=: REQUEST","incorrect":"        -> request as HTTPRequest :=: REQUEST \"not-required\"","explanation":"REQUEST binds the whole HTTP request and CONTENT binds the whole request body, so neither accepts a qualifier name. The quoted qualifier (only valid for PATH, QUERY and HEADER, which need to name the segment/key/header) is rejected as E07720. Remove the qualifier so the binding reads ':=: REQUEST'. See ek9 -h E07720 for details."}],"companions":[]}
{"id":1326,"category":"Web Services","question":"Why does EK9 reject a service method that declares a return but has no implementation body?","url":"https://ek9.io/qa/QA1326.html","alternatePhrasings":["What triggers E07740 - implementation not provided, services cannot be abstract?","Why can't an EK9 service method be left abstract with just a return declaration?","How do I fix 'implementation not provided' on a service endpoint in EK9?"],"answer":"Services in EK9 are CONCRETE entry points - every endpoint method must supply a real implementation body. Declaring only the return (for example '<- response as HTTPResponse?') with no statements that build the response leaves the method effectively abstract, and EK9 raises E07740 because services cannot be abstract. The runtime must be able to invoke every route, so there is no such thing as an abstract service method.\n\nThe fix is to provide a body that materialises the HTTPResponse, typically a dynamic class implementing the HTTPResponse trait with overrides for content, status, contentType, cacheControl and contentLanguage.\n\nE07740 differs from E07800: E07800 fires when there is no return declaration at all, whereas E07740 fires when the return is declared but no implementation is provided. See Q685 for service method body rules. See Q869 for the missing-return case (E07800).","ek9Example":"defines module qa.web.servicemethodbody\n\n  defines service\n\n    StatusService :/status open\n\n      //CORRECT: the endpoint provides a concrete implementation body that\n      //materialises an HTTPResponse. Leaving only the '<- response as HTTPResponse?'\n      //declaration with no body would raise E07740 (services cannot be abstract).\n      health() as GET for :/health\n        <- response as HTTPResponse: () with trait HTTPResponse\n          override content()\n            <- rtn as String: `{\"status\": \"healthy\"}`\n          override status() as pure\n            <- rtn as Integer: 200\n          override contentType() as pure\n            <- rtn as String: \"application/json\"\n          override cacheControl() as pure\n            <- rtn as String: \"no-cache\"\n          override contentLanguage() as pure\n            <- rtn as String: \"en\"\n          default operator ?\n\n  defines application\n\n    StatusApp\n      register StatusService()\n\n  defines program\n\n    ServiceMethodBodyDemo()\n      stdout <- Stdout()\n      stdout.println(\"Service registered with application\")\n      stdout.println(\"GET /status/health returns a concrete HTTPResponse\")","migrationContext":"Java (JAX-RS/Spring): a controller method can compile as an abstract or interface declaration with no body, deferring the implementation - the gap surfaces only at runtime wiring. Kotlin/C#: abstract handler signatures are equally legal in base classes. EK9: a service is a dedicated concrete entry-point construct, so a route declared without an implementation body is rejected at compile time with E07740 - there are no abstract service methods.","keywords":["E07740","HTTPResponse","abstract","body","concrete","endpoint","implementation","method","rest","service","web"],"primaryTopics":["service method body","E07740","services cannot be abstract"],"typicalErrors":[{"error":"E07740","correct":"health() as GET for :/health\n        <- response as HTTPResponse: () with trait HTTPResponse\n          override content()\n            <- rtn as String: `{\"status\": \"healthy\"}`\n          override status() as pure\n            <- rtn as Integer: 200","incorrect":"health() as GET for :/health\n  <- response as HTTPResponse?","explanation":"Declaring only the return type with no statements that build the response leaves the service method without an implementation, which EK9 treats as abstract and rejects with E07740 because services cannot be abstract. Provide a concrete body that materialises the HTTPResponse (a dynamic class implementing the HTTPResponse trait). See ek9 -h E07740 for details."}],"companions":[]}
{"id":1327,"category":"Web Services","question":"Why must a service parameter bound with :=: REQUEST be typed as HTTPRequest in EK9?","url":"https://ek9.io/qa/QA1327.html","alternatePhrasings":["What triggers E07770 SERVICE_INCOMPATIBLE_PARAM_TYPE_REQUEST?","Why can't I bind :=: REQUEST to a String parameter?","What type does the :=: REQUEST binding require in an EK9 service?"],"answer":"The :=: REQUEST binding hands the service method the entire HTTP request object, so the parameter it binds to must be typed as HTTPRequest. Binding REQUEST to any other type (String, Integer, a record, etc.) triggers E07770 because the compiler cannot deliver a full request into a parameter that is not an HTTPRequest.\n\nCORRECT PATTERN\n  handle() as GET for :/\n    -> request as HTTPRequest :=: REQUEST\nThe single parameter receives the whole request.\n\nINCORRECT PATTERN\n  handle() as GET for :/\n    -> request as String :=: REQUEST\nString cannot receive a full request, so E07770 is raised.\n\nThis is the mirror of E07790 (HTTPRequest used with a non-REQUEST binding such as PATH or QUERY): REQUEST and HTTPRequest must always go together, and a REQUEST parameter must stand by itself in the method. For individual values use :=: PATH, :=: QUERY or :=: HEADER with simple parseable types like Integer or String.\n\nSee Q868 for the inverse restriction. See Q202 for parameter binding. See Q112 for a full service example.","ek9Example":"defines module qa.webdeep.service.request.binding\n\n  defines constant\n\n    JSON_TYPE <- \"application/json\"\n\n  defines service\n\n    <?-\n      A service whose handler needs the full request object.\n      The :=: REQUEST binding requires an HTTPRequest parameter.\n    -?>\n    EchoService :/echo open\n\n      handle() as GET for :/\n        -> request as HTTPRequest :=: REQUEST\n        <- response as HTTPResponse: (body: request.content()) with trait HTTPResponse\n          override content()\n            <- rtn as String: `{\"received\": \"${body}\"}`\n          override status() as pure\n            <- rtn as Integer: 200\n          override contentType() as pure\n            <- rtn as String: JSON_TYPE\n          override cacheControl() as pure\n            <- rtn as String: \"no-cache\"\n          override contentLanguage() as pure\n            <- rtn as String: \"en\"\n          default operator ?\n\n  defines application\n\n    EchoApp\n      register EchoService()\n\n  defines program\n\n    RequestBindingDemo()\n      stdout <- Stdout()\n      stdout.println(\"REQUEST binding requires an HTTPRequest parameter.\")\n      stdout.println(\"For individual values use PATH, QUERY or HEADER with simple types.\")","migrationContext":"Java Spring: a controller can inject HttpServletRequest or bind individual values via @RequestParam/@PathVariable, with no compile-time link between the binding annotation and the parameter type. C# ASP.NET: HttpRequest and model binding mix freely. Go: handlers always receive *http.Request and parse manually. EK9: the binding keyword and the parameter type are checked together at compile time, so REQUEST is only ever allowed against an HTTPRequest parameter.","keywords":["E07770","HTTPRequest","REQUEST","binding","http","parameter","service","type"],"primaryTopics":["service REQUEST binding","E07770","SERVICE_INCOMPATIBLE_PARAM_TYPE_REQUEST"],"typicalErrors":[{"error":"E07770","correct":"        -> request as HTTPRequest :=: REQUEST","incorrect":"        -> request as String :=: REQUEST","explanation":"The :=: REQUEST binding delivers the entire HTTP request, so its parameter must be typed as HTTPRequest. Binding REQUEST to a String (or any non-HTTPRequest type) cannot receive a full request and raises E07770. Type the parameter as HTTPRequest, or for individual values use :=: PATH/QUERY/HEADER with simple types. See ek9 -h E07770 for details."}],"companions":[]}
{"id":1328,"category":"Web Services","question":"Why can't I mix HTTPRequest with other parameters in an EK9 service operation?","url":"https://ek9.io/qa/QA1328.html","alternatePhrasings":["What triggers E07780 SERVICE_REQUEST_BY_ITSELF?","Why must HTTPRequest be the only parameter on a service operation?","How do I get a path or query value when I also need the full HTTPRequest?"],"answer":"When a service operation takes the full request via :=: REQUEST (an HTTPRequest), that parameter must be the ONLY parameter on the operation. Adding any second parameter — a CONTENT binding, a PATH segment, anything — triggers E07780 (SERVICE_REQUEST_BY_ITSELF).\n\nThe rule exists because HTTPRequest already gives you the entire request — path segments, query string, headers and body. Mixing it with individually-bound parameters duplicates the binding and creates two sources of truth for the same data.\n\nCORRECT PATTERN\n  process() as POST for :/process\n    -> request as HTTPRequest :=: REQUEST\nRead the body, headers and query values from the request inside the method body via request.content() and friends.\n\nINCORRECT PATTERN\n  process() as POST for :/process\n    ->\n      request as HTTPRequest :=: REQUEST\n      body as String :=: CONTENT\nThe extra body parameter alongside the full request raises E07780.\n\nSee Q202 for parameter binding. See Q868 for valid path-parameter types.","ek9Example":"defines module qa.webdeep.service.request.only\n\n  defines constant\n\n    JSON_RESPONSE <- \"application/json\"\n\n  defines service\n\n    <?-\n      Operation that binds the FULL request via :=: REQUEST.\n      HTTPRequest is the only parameter, so E07780 is not raised.\n      Any path/query/body value is read from the request inside the body.\n    -?>\n    ItemService :/items open\n\n      process() as POST for :/process\n        -> request as HTTPRequest :=: REQUEST\n        <- response as HTTPResponse: (request: request) with trait HTTPResponse\n          override content()\n            <- rtn as String: `{\"received\": \"${request.content()}\"}`\n          override status() as pure\n            <- rtn as Integer: 201\n          override contentType() as pure\n            <- rtn as String: JSON_RESPONSE\n          override cacheControl() as pure\n            <- rtn as String: \"no-store\"\n          override contentLanguage() as pure\n            <- rtn as String: \"en\"\n          default operator ?\n\n  defines application\n\n    ItemApp\n      register ItemService()\n\n  defines program\n\n    HttpRequestOnlyDemo()\n      stdout <- Stdout()\n      stdout.println(\"HTTPRequest must be the ONLY parameter on an operation.\")\n      stdout.println(\"Read path/query/body values from the request inside the body.\")","migrationContext":"Java Spring: a handler may freely mix HttpServletRequest with @PathVariable/@RequestParam — nothing prevents the duplication. C# ASP.NET: HttpContext plus bound parameters coexist. Go net/http: you read *http.Request and also parse mux.Vars(r) together. EK9: the compiler forbids the mixture at compile time — if you take the full HTTPRequest you take only that and extract the rest from it.","keywords":["E07780","HTTPRequest","REQUEST","binding","http","parameter","rest","service"],"primaryTopics":["HTTPRequest sole parameter","E07780","SERVICE_REQUEST_BY_ITSELF"],"typicalErrors":[{"error":"E07780","correct":"      process() as POST for :/process\n        -> request as HTTPRequest :=: REQUEST","incorrect":"      process() as POST for :/process\n        ->\n          request as HTTPRequest :=: REQUEST\n          body as String :=: CONTENT","explanation":"An operation that binds the full request with :=: REQUEST must have HTTPRequest as its ONLY parameter. The extra CONTENT-bound body parameter makes the operation take more than one parameter, so E07780 fires. Drop the extra parameter and read the body from the request inside the method body via request.content(). See ek9 -h E07780 for details."}],"companions":[]}
{"id":1329,"category":"Web Services","question":"Why must a CONTEXT-bound service parameter be typed HTTPContext in EK9?","url":"https://ek9.io/qa/QA1329.html","alternatePhrasings":["What triggers E07791 wrong type for CONTEXT binding?","Why can't I bind :=: CONTEXT to a String parameter in an EK9 service?","What is the difference between CONTENT and CONTEXT binding in EK9 services?"],"answer":"The CONTEXT qualifier gives a service operation access to the request context (authenticated principal, security state, connection metadata), so the bound parameter MUST be typed HTTPContext. Binding :=: CONTEXT to a String (or any other type) triggers E07791 because the framework supplies a context object, not a parsed value.\n\nCORRECT PATTERN\n  info() as GET for :/data\n    -> ctx as HTTPContext :=: CONTEXT\n\nINCORRECT PATTERN\n  info() as GET for :/data\n    -> ctx as String :=: CONTEXT\n\nDo not confuse CONTENT with CONTEXT. CONTENT binds the request body, which IS a String; CONTEXT binds request metadata, which is an HTTPContext. For individual request values use the specific access qualifiers (:=: QUERY, :=: PATH).\n\nSee Q202 for parameter binding. See Q868 for parameter type rules.","ek9Example":"defines module qa.webdeep.service.context.binding\n\n  defines constant\n\n    JSON_RESPONSE <- \"application/json\"\n\n  defines service\n\n    <?-\n      Service using CONTEXT binding correctly: the parameter is typed\n      HTTPContext, which is what the framework supplies for :=: CONTEXT.\n    -?>\n    InfoService :/info open\n\n      info() as GET for :/data\n        ->\n          ctx as HTTPContext :=: CONTEXT\n        <- response as HTTPResponse: (capturedCtx: ctx) with trait HTTPResponse\n          override content()\n            <- rtn as String: `{\"hasContext\": ${capturedCtx?}}`\n          override status() as pure\n            <- rtn as Integer: 200\n          override contentType() as pure\n            <- rtn as String: JSON_RESPONSE\n          override cacheControl() as pure\n            <- rtn as String: \"no-cache\"\n          override contentLanguage() as pure\n            <- rtn as String: \"en\"\n          default operator ?\n\n  defines application\n\n    InfoApp\n      register InfoService()\n\n  defines program\n\n    ContextBindingDemo()\n      stdout <- Stdout()\n      stdout.println(\"CONTEXT binds an HTTPContext (request metadata).\")\n      stdout.println(\"CONTENT binds a String (the request body).\")","migrationContext":"Java Spring: SecurityContextHolder / Principal injected as method args, validated only at runtime. C# ASP.NET: HttpContext available ambiently, no compile-time type checking of binding. Go: context.Context passed manually with no framework guard. EK9: the compiler enforces at compile time that a CONTEXT-bound parameter is exactly HTTPContext, eliminating mismatched-binding bugs before the service runs.","keywords":["CONTENT","CONTEXT","E07791","HTTPContext","binding","request","service","type"],"primaryTopics":["CONTEXT binding type","E07791","HTTPContext"],"typicalErrors":[{"error":"E07791","correct":"->\n          ctx as HTTPContext :=: CONTEXT","incorrect":"        -> ctx as String :=: CONTEXT","explanation":"The CONTEXT qualifier supplies the request context object (principal, security state, connection metadata), so the parameter must be typed HTTPContext. Binding :=: CONTEXT to a String triggers E07791. CONTENT binds the body (a String); CONTEXT binds metadata (an HTTPContext) — do not confuse them. See ek9 -h E07791 for details."}],"companions":[]}
{"id":1330,"category":"Web Services","question":"Why can't I mix HTTPContext with QUERY or PATH parameters in an EK9 service method?","url":"https://ek9.io/qa/QA1330.html","alternatePhrasings":["What triggers E07792 HTTPContext mixed with other params?","Why must HTTPContext be the only parameter in a service method?","How do I access query and path values when using HTTPContext in EK9?"],"answer":"When an EK9 service method binds the full request via :=: CONTEXT, the HTTPContext parameter must be the ONLY parameter. HTTPContext already exposes the complete request - query string, path segments, headers, body - so adding individual :=: QUERY or :=: PATH bindings alongside it is redundant and ambiguous, raising E07792.\n\nYou have two valid choices:\n1. Use HTTPContext ALONE and extract everything you need from it inside the method body via its request accessors.\n2. Use only individual qualified parameters (:=: PATH, :=: QUERY, :=: HEADER, :=: CONTENT) and do NOT take an HTTPContext at all.\n\nSee Q202 for parameter binding and Q945 for parameter qualifiers.","ek9Example":"defines module qa.webdeep.service.httpcontext\n\n  defines constant\n\n    JSON_TYPE <- \"application/json\"\n\n  defines service\n\n    <?-\n      Search service demonstrating the correct use of HTTPContext.\n      The method binds the full request via :=: CONTEXT and uses ONLY\n      that single parameter. Any query or path values are read from the\n      context inside the method body - never via a second :=: QUERY/PATH\n      binding, which would raise E07792.\n    -?>\n    SearchService :/search open\n\n      search() as GET for :/results\n        -> ctx as HTTPContext :=: CONTEXT\n        <- response as HTTPResponse: (reqId: ctx.requestId()) with trait HTTPResponse\n          override content()\n            <- rtn as String: `{\"requestId\": \"${reqId}\"}`\n          override status() as pure\n            <- rtn as Integer: 200\n          override contentType() as pure\n            <- rtn as String: JSON_TYPE\n          override cacheControl() as pure\n            <- rtn as String: \"no-cache\"\n          override contentLanguage() as pure\n            <- rtn as String: \"en\"\n          default operator ?\n\n  defines application\n\n    SearchApp\n      register SearchService()\n\n  defines program\n\n    HttpContextDemo()\n      stdout <- Stdout()\n      stdout.println(\"HTTPContext must be the ONLY service-method parameter.\")\n      stdout.println(\"Read query and path values from the context inside the body,\")\n      stdout.println(\"never via a second :=: QUERY or :=: PATH binding (E07792).\")","migrationContext":"Java Spring: you may freely combine HttpServletRequest with @RequestParam/@PathVariable in the same handler - no compiler objection, even though the request object already contains those values. JAX-RS allows @Context HttpServletRequest beside @QueryParam. EK9 instead makes the redundancy a compile-time error (E07792): the all-encompassing HTTPContext and individual bindings are mutually exclusive, so a method has exactly one, unambiguous binding strategy.","keywords":["CONTEXT","E07792","HTTPContext","PATH","QUERY","binding","parameter","service","web"],"primaryTopics":["HTTPContext binding","E07792","service parameter mixing"],"typicalErrors":[{"error":"E07792","correct":"        -> ctx as HTTPContext :=: CONTEXT","incorrect":"        ->\n          ctx as HTTPContext :=: CONTEXT\n          term as String :=: QUERY \"q\"","explanation":"HTTPContext (:=: CONTEXT) already provides the entire request, so adding a second :=: QUERY binding alongside it is redundant and ambiguous, triggering E07792. See ek9 -h E07792 for details."}],"companions":[]}
{"id":1331,"category":"Web Services","question":"Why can't I bind HTTPContext to a QUERY parameter in an EK9 service?","url":"https://ek9.io/qa/QA1331.html","alternatePhrasings":["What triggers E07793 when using HTTPContext in a service parameter?","Which access type must HTTPContext use in EK9 services?","Why does EK9 reject HTTPContext with :=: QUERY, PATH, HEADER or CONTENT?"],"answer":"HTTPContext is the whole-request context object, so it can only be bound with the :=: CONTEXT access type. Binding it with :=: QUERY, :=: PATH, :=: HEADER or :=: CONTENT triggers E07793 because those access types extract a single named value from the URI, path, headers or body — they cannot reconstruct the full context object.\n\nCORRECT PATTERN\n  info() as GET for :/info\n    -> ctx as HTTPContext :=: CONTEXT\nHTTPContext bound with CONTEXT gives access to the entire request context.\n\nIf you only need a single query value, bind a simple type instead:\n  search() as GET for :/search\n    -> term as String :=: QUERY \"q\"\n\nSee Q202 for parameter binding. See Q868 for the related HTTPRequest restriction. See Q112 for a full service example.","ek9Example":"defines module qa.webdeep.service.httpcontext.access\n\n  defines constant\n\n    PLAIN_TEXT <- \"text/plain\"\n\n  defines service\n\n    <?-\n      Service showing the two correct bindings: HTTPContext must use\n      :=: CONTEXT, while a single query value uses a simple String type.\n    -?>\n    Info :/info open\n\n      context() as GET for :/context\n        -> ctx as HTTPContext :=: CONTEXT\n        <- response as HTTPResponse: (capturedId: ctx.requestId()) with trait HTTPResponse\n          override content()\n            <- rtn as String: `context request id ${capturedId}`\n          override status() as pure\n            <- rtn as Integer: 200\n          override contentType() as pure\n            <- rtn as String: PLAIN_TEXT\n          override cacheControl() as pure\n            <- rtn as String: \"no-store\"\n          override contentLanguage() as pure\n            <- rtn as String: \"en\"\n          default operator ?\n\n      search() as GET for :/search\n        -> term as String :=: QUERY \"q\"\n        <- response as HTTPResponse: (capturedTerm: term) with trait HTTPResponse\n          override content()\n            <- rtn as String: `searched for ${capturedTerm}`\n          override status() as pure\n            <- rtn as Integer: 200\n          override contentType() as pure\n            <- rtn as String: PLAIN_TEXT\n          override cacheControl() as pure\n            <- rtn as String: \"no-store\"\n          override contentLanguage() as pure\n            <- rtn as String: \"en\"\n          default operator ?\n\n  defines application\n\n    InfoApp\n      register Info()\n\n  defines program\n\n    HttpContextAccessDemo()\n      stdout <- Stdout()\n      stdout.println(\"HTTPContext must bind with :=: CONTEXT\")\n      stdout.println(\"A single query value binds with :=: QUERY using a simple type\")","migrationContext":"Java Spring: any controller argument can be annotated freely (@RequestParam, @RequestBody) with no compile-time check that the type matches the binding source. C# ASP.NET: model binding is convention-based and resolved at runtime. Go/Python: the request context is read manually with no binding metadata. EK9: the compiler enforces at compile time that HTTPContext is only ever bound with :=: CONTEXT, so a context object can never be wired to a query, path, header or body source.","keywords":["CONTEXT","E07793","HTTPContext","QUERY","access","binding","http","rest","service"],"primaryTopics":["HTTPContext access type","E07793","service parameter binding"],"typicalErrors":[{"error":"E07793","correct":"        -> ctx as HTTPContext :=: CONTEXT","incorrect":"        -> ctx as HTTPContext :=: QUERY \"q\"","explanation":"HTTPContext is the whole-request context object and can only be bound with the :=: CONTEXT access type. Binding it with :=: QUERY (or PATH, HEADER, CONTENT) extracts a single named value and cannot produce a context object, so the compiler raises E07793. Use :=: CONTEXT for HTTPContext, or bind a simple type like String for a query value. See ek9 -h E07793 for details."}],"companions":[]}
{"id":1332,"category":"Security and Sanitization","question":"Why can't I mark a captured variable as 'sanitized' in a dynamic function?","url":"https://ek9.io/qa/QA1332.html","alternatePhrasings":["What triggers E07941 SANITIZED_NOT_ON_CAPTURED?","How do I sanitize a value before capturing it into a closure?","Why is 'sanitized' rejected on a dynamic function or class capture list?"],"answer":"The 'sanitized' modifier cannot be applied to a captured variable in a dynamic function or dynamic class (E07941). By the time a value is captured into a closure it has already crossed the trust boundary, so re-asserting sanitization there is both meaningless and misleading.\n\nSanitization is a property of the ENTRY POINT - the function or method parameter where untrusted external data first enters the system. Mark that parameter 'sanitized' and the compiler injects a defensive sanitizing copy. The dynamic function then captures the already-clean value with a plain capture (no modifier).\n\nWRONG\n  build()\n    -> raw as String\n    fn <- (sanitized raw) is Formatter as pure function ...   // E07941\n\nRIGHT\n  build()\n    -> raw as sanitized String          // sanitize at the boundary\n    fn <- (raw) is Formatter as pure function ...   // capture the clean value\n\nSee Q950 for the full set of places 'sanitized' is restricted (captures E07941, declarations E07943).","ek9Example":"defines module qa.sanitizeddeep.captureboundary\n\n  defines function\n\n    <?-\n      Abstract function contract the dynamic function will implement.\n    -?>\n    Formatter as pure abstract\n      -> text as String\n      <- output as String?\n\n    <?-\n      THE FIX: sanitize at the ENTRY POINT (the parameter), not at the capture.\n      The compiler injects a sanitizing defensive copy for 'rawInput' here.\n      The dynamic function then captures the already-clean value with a plain\n      capture list (no 'sanitized' modifier - that would be E07941).\n    -?>\n    buildPrefixer() as pure\n      -> rawInput as sanitized String\n      <- made as Formatter?\n\n      made: (prefix: rawInput) is Formatter as pure function\n        output: `${prefix}: ${text}`\n\n  defines program\n\n    SanitizedCaptureDemo()\n      stdout <- Stdout()\n\n      formatter <- buildPrefixer(\"user-data\")\n      stdout.println(formatter(\"hello\"))\n      stdout.println(\"sanitize at the parameter, capture the clean value\")","migrationContext":"Java/Kotlin/Python: no language-level taint tracking - a closure can capture tainted data and developers must remember to sanitize manually somewhere, with no compiler check. EK9 forces sanitization to live at the single entry-point parameter and rejects it on captures at compile time (E07941), so the trust boundary stays in exactly one obvious place.","keywords":["E07941","boundary","capture","captured","closure","dynamic","function","sanitized","security","trust"],"primaryTopics":["sanitized on capture","E07941","dynamic function capture"],"typicalErrors":[{"error":"E07941","correct":"made: (prefix: rawInput) is Formatter as pure function","incorrect":"made: (prefix: sanitized rawInput) is Formatter as pure function","explanation":"'sanitized' cannot be applied to a captured variable - it belongs on the entry-point parameter where untrusted data enters. See ek9 -h E07941 for details."}],"companions":[]}
{"id":1333,"category":"Comparison Patterns","question":"Why does EK9 reject a coalescing expression where both sides are literals?","url":"https://ek9.io/qa/QA1333.html","alternatePhrasings":["What triggers E08094 when using <? or >? with two literal values?","Why can't I write 100 <? 200 in EK9?","How do I fix a coalescing minimum or maximum that has a predetermined result?"],"answer":"EK9 detects when both operands of a coalescing operator (<?, >?, <=?, >=?, ?:, ??) are literal values and raises E08094. Because both sides are known at compile time, the coalescing result is predetermined - the expression is dead code and almost always a copy-paste error or unfinished placeholder.\n\nTHE PROBLEM\nThe coalescing minimum '100 <? 200' always yields 100; the maximum '100 >? 200' always yields 200. Writing this is no different from assigning the answer directly, so the operator serves no purpose.\n\nCORRECT PATTERN\nKeep at least one operand a variable or named constant so the result genuinely depends on runtime values:\n  best <- onlinePrice <? FLOOR_PRICE\n  cap  <- requested >? MAX_LIMIT\nIf you really want a fixed value, assign it directly: result: 100.\n\nSee Q638 for literal-vs-literal comparison detection (E08082). See Q963 for how the <? coalescing minimum operator works.","ek9Example":"defines module qa.comparison.constant.coalescing\n\n  defines constant\n\n    FLOOR_PRICE <- 50\n\n    MAX_LIMIT <- 200\n\n  defines function\n\n    <?-\n      Correct: coalescing minimum between a variable and a named constant.\n      Replacing onlinePrice with a literal would trigger E08094.\n    -?>\n    bestPrice() as pure\n      -> onlinePrice as Integer\n      <- best as Integer: onlinePrice <? FLOOR_PRICE\n\n    <?-\n      Correct: coalescing maximum between a variable and a named constant.\n    -?>\n    cappedRequest() as pure\n      -> requested as Integer\n      <- cap as Integer: requested >? MAX_LIMIT\n\n  defines program\n\n    ConstantCoalescingDemo()\n      stdout <- Stdout()\n\n      stdout.println(`Best price: ${bestPrice(75)}`)\n      stdout.println(`Best price: ${bestPrice(40)}`)\n      stdout.println(`Capped: ${cappedRequest(150)}`)\n      stdout.println(`Capped: ${cappedRequest(250)}`)","migrationContext":"Java: has no coalescing minimum/maximum operator and no detection of predetermined expressions; Math.min(100, 200) compiles silently. Kotlin/Swift: chained ?: and ?? with two constants compile without warning. Rust: clippy flags some absurd constant comparisons but not coalescing. EK9: compile-time error (E08094) whenever both coalescing operands are literals.","keywords":["E08094","coalescing","code","comparison","constant","dead","literal","maximum","minimum","pattern","predetermined"],"primaryTopics":["constant coalescing","predetermined expression","E08094"],"typicalErrors":[{"error":"E08094","correct":"best as Integer: onlinePrice <? FLOOR_PRICE","incorrect":"best as Integer: 100 <? 200","explanation":"Both operands of the <? coalescing minimum are literals, so the result is fixed at compile time (always 100) - dead code that usually means a copy-paste slip or unfinished placeholder. Keep at least one side a variable or named constant, or assign the intended value directly. See ek9 -h E08094 for details."}],"companions":[]}
{"id":1334,"category":"DI Validation","question":"Why does EK9 reject two 'register' lines for the same abstract component type?","url":"https://ek9.io/qa/QA1334.html","alternatePhrasings":["What triggers E08230 duplicate registration in an application?","Why can I only register one implementation per abstract type per application?","How do I wire two implementations of one interface in EK9?"],"answer":"Within a single application block each abstract component type may have exactly ONE concrete registration. A second 'register ... as <SameAbstractType>' raises E08230 (duplicate registration). The reason is resolution: when a program injects 'cache as Cache!', the compiler must bind that injection point to exactly one concrete implementation. Two registrations for Cache would make that binding ambiguous, so EK9 rejects it at compile time rather than picking arbitrarily or failing at runtime.\n\nThe fix is to keep one binding per abstract type in the application. If you genuinely need a different implementation (production vs test), declare a SEPARATE application, each with its own single registration, and select the application with 'with application of'.\n\nSee Q673 for missing registration (E08210). See Q674 for the same rule from the ambiguity angle. See Q671 for circular dependencies.","ek9Example":"defines module qa.divalidation.duplicateregistration\n\n  defines component\n\n    <?-\n      Abstract cache contract.\n    -?>\n    Cache as abstract\n\n      store() as abstract\n        ->\n          key as String\n          payload as String\n\n      default operator ?\n\n    <?-\n      Redis-backed cache for production.\n    -?>\n    RedisCache extends Cache\n\n      override store()\n        ->\n          key as String\n          payload as String\n        stdout <- Stdout()\n        stdout.println(`Redis store ${key}=${payload}`)\n\n      default operator ?\n\n    <?-\n      In-memory cache for tests.\n    -?>\n    MemoryCache extends Cache\n\n      override store()\n        ->\n          key as String\n          payload as String\n        stdout <- Stdout()\n        stdout.println(`Memory store ${key}=${payload}`)\n\n      default operator ?\n\n  defines application\n\n    <?-\n      Production wiring: exactly ONE registration for Cache.\n    -?>\n    ProdApp\n      register RedisCache() as Cache\n\n    <?-\n      Test wiring: same abstract type, different implementation,\n      in a SEPARATE application. One binding each, no E08230.\n    -?>\n    TestApp\n      register MemoryCache() as Cache\n\n  defines program\n\n    DuplicateRegistrationDemo() with application of ProdApp\n      stdout <- Stdout()\n\n      cache as Cache!\n      cache.store(\"user:1\", \"Alice\")\n\n      stdout.println(\"One abstract type = one registration per application\")","migrationContext":"Java/Spring: two beans of one type need @Primary or @Qualifier, otherwise NoUniqueBeanDefinitionException at startup (runtime). Guice: a duplicate bind throws CreationException at injector build time. Kotlin/Koin: last definition silently overrides earlier ones. EK9: a duplicate registration for the same abstract type in one application is a compile-time error (E08230); use separate applications for separate wiring.","keywords":["DI","E08230","abstract","ambiguous","application","binding","component","duplicate","inject","register","registration"],"primaryTopics":["duplicate registration","E08230","one binding per abstract type"],"typicalErrors":[{"error":"E08230","correct":"    ProdApp\n      register RedisCache() as Cache\n\n    <?-","incorrect":"BrokenApp\n      register RedisCache() as Cache\n      register MemoryCache() as Cache","explanation":"Both 'register' lines bind the same abstract type Cache inside one application, so an injection point 'cache as Cache!' has two candidate implementations and cannot be resolved unambiguously - this raises E08230. Keep one registration per abstract type per application; if you need a different implementation, put it in a separate application (ProdApp vs TestApp) and select it with 'with application of'. See ek9 -h E08230 for details."}],"companions":[]}
{"id":1335,"category":"Code Quality","question":"Why does EK9 reject a method or function that has too many statements?","url":"https://ek9.io/qa/QA1335.html","alternatePhrasings":["What triggers E11012 EXCESSIVE_STATEMENT_COUNT in EK9?","How do I fix a function the compiler says has too many statements?","What is the statement-count limit for functions and methods in EK9?"],"answer":"EK9 measures a raw SIZE metric - the number of statements in each function, method, operator and service operation - separately from cyclomatic complexity (E11010). Each variable declaration, assignment, control-flow entry, throw and call counts as one statement. When the count exceeds the threshold the compiler raises E11012. The thresholds are: operators 50, methods 100, functions 150, service operations 100.\n\nA long function is doing too much, so the fix is to identify logical groups of statements and extract each into a well-named helper function with a single responsibility. The original function then becomes a short sequence of calls, and every unit drops well under the threshold.\n\nNote E11012 is a pure size check: it fires even when each statement is trivial and the cyclomatic complexity is low. See Q696 for the complexity-limit catalogue. See Q1020 for refactoring techniques. See Q311 for quality checks.","ek9Example":"defines module qa.quality.statement.count\n\n  defines function\n\n    //FIX: each logical sub-task lives in its own small focused function,\n    //so no single function approaches the 150-statement threshold.\n\n    sumValues() as pure\n      -> values as List of Integer\n      <- rtn as Integer: 0\n      for value in values\n        rtn: rtn + value\n\n    averageValue() as pure\n      ->\n        values as List of Integer\n        total as Integer\n      <- rtn as Integer: 0\n      count <- length values\n      if count > 0\n        rtn: total / count\n\n    formatReport() as pure\n      ->\n        total as Integer\n        average as Integer\n      <- rtn as String: `total=${total} average=${average}`\n\n    //The orchestrating function is now a short sequence of calls,\n    //well under the statement-count limit.\n    buildReport() as pure\n      -> values as List of Integer\n      <- rtn as String?\n      total <- sumValues(values)\n      average <- averageValue(values, total)\n      rtn: formatReport(total, average)\n\n  defines program\n\n    StatementCountDemo()\n      stdout <- Stdout()\n      values <- [10, 20, 30, 40]\n      stdout.println(buildReport(values))","migrationContext":"Java: method length is unbounded; only optional tools (Checkstyle MethodLength, PMD ExcessiveMethodLength, SonarQube) flag long methods, and they emit warnings teams routinely suppress. Kotlin/Swift/Go: no built-in statement-count limit; detekt/SwiftLint can warn if configured. EK9: statement count is a hard compile error (E11012) per function/method/operator/service-operation, so an oversized unit simply does not compile.","keywords":["E11012","count","decompose","extract","function","method","quality","size","statement"],"primaryTopics":["excessive statement count","E11012","function extraction"],"typicalErrors":[{"error":"E11012","correct":"    buildReport() as pure\n      -> values as List of Integer\n      <- rtn as String?\n      total <- sumValues(values)\n      average <- averageValue(values, total)\n      rtn: formatReport(total, average)","incorrect":"buildReport()\n  -> values as List of Integer\n  <- rtn as String?\n  // 160+ inline statements computing the total, the average and the\n  // formatted text all in one body","explanation":"Putting 160+ statements directly in one function exceeds the function threshold of 150 (methods 100, operators 50, service operations 100), so the compiler raises E11012 - a size check distinct from cyclomatic complexity. Identify logical groups (sum, average, format) and extract each into its own named helper, then call them in sequence; every unit then falls under the threshold. See ek9 -h E11012 for details."}],"companions":[]}
{"id":1337,"category":"Code Quality","question":"Why does EK9 reject a module whose constructs are all unrelated?","url":"https://ek9.io/qa/QA1337.html","alternatePhrasings":["What triggers E11017 poor module cohesion?","Why must I split a grab-bag utilities module in EK9?","How does EK9 measure module construct disconnection?"],"answer":"EK9 measures how connected the constructs inside one module are. Two constructs are 'connected' when one references the other's type. A grab-bag module - String helpers next to maths helpers next to date helpers, none sharing any types - forms many disconnected groups. When a module exceeds ALL THREE thresholds at once (more than 88% disconnection AND more than 30 disconnected groups AND more than 60 constructs) EK9 raises E11017, because the module is really several modules glued together.\n\nThe fix is the Split Module refactoring: gather constructs that share types into focused modules (qa.text, qa.geometry, qa.time, ...), each with one clear purpose. Constructs that share no types probably do not belong together. The example below is one such cohesive module - every construct references the Point type, so its constructs form a single connected group and cohesion is high.\n\nSee Q314 for how EK9 measures cohesion and coupling. See Q311 for the full quality-check catalog.","ek9Example":"defines module qa.geometry.points\n\n  defines record\n\n    <?-\n      Shared type that binds every construct in this module together.\n      Because the function and the program both reference Point, the\n      module forms a single connected group - high cohesion, no E11017.\n    -?>\n    Point\n      x as Float: Float()\n      y as Float: Float()\n\n      Point() as pure\n        ->\n          initialX as Float\n          initialY as Float\n        this.x :=? initialX\n        this.y :=? initialY\n\n      operator $ as pure\n        <- rtn as String: `(${x}, ${y})`\n\n      default operator ?\n\n  defines function\n\n    //Operates on the shared Point type, keeping the module cohesive.\n    distanceFromOrigin() as pure\n      -> point as Point\n      <- rtn as Float: Float()\n      sumOfSquares <- point.x * point.x + point.y * point.y\n      rtn: sumOfSquares.sqrt()\n\n  defines program\n\n    ModuleCohesionDemo()\n      stdout <- Stdout()\n      origin <- Point(3.0, 4.0)\n      stdout.println(`Point ${origin} is ${distanceFromOrigin(origin)} from origin`)","migrationContext":"Java: package contents are unconstrained; cohesion is only reported (LCOM, package-tangle) by SonarQube/JDepend as advisory metrics, never enforced. Kotlin/C#/Go: no module-cohesion enforcement - a 'utils' grab-bag compiles silently. EK9: low module cohesion is a hard compiler error (E11017) once the disconnection, group-count, and construct-count thresholds are all exceeded, forcing the Split Module refactoring.","keywords":["E11017","cohesion","connected","disconnection","grab-bag","group","module","quality","split","utilities"],"primaryTopics":["module cohesion","E11017","split module"],"typicalErrors":[],"companions":[]}
{"id":1338,"category":"Code Quality","question":"Why does EK9 report E11018 for an unused captured variable in a dynamic function?","url":"https://ek9.io/qa/QA1338.html","alternatePhrasings":["What triggers E11018 unused captured variable?","Why must every variable I capture in a closure be used in the body?","How do I fix an unused capture in a dynamic function?"],"answer":"EK9 dynamic functions capture variables EXPLICITLY, listing each one in parentheses before the 'is' keyword. Because capture is explicit and by value, every captured variable represents a deliberate dependency that is copied into the closure. If a captured variable is never referenced inside the function body, that capture is dead weight: it adds an unnecessary dependency, copies a value for no reason, and signals a copy-paste or refactoring mistake. EK9 raises E11018 (unused captured variable) at compile time.\n\nThe fix is to remove the unused variable from the capture list. Keep only the variables the body actually reads. If you genuinely need the value later, use it in the body; otherwise drop it from the capture list entirely.\n\nThis sits alongside E08090 (unused local variable) and E08091 (unused parameter): EK9 treats unused captures, locals, and parameters all as compile-time errors so that declared dependencies and real usage never drift apart.\n\nSee Q53 for closure capture mechanics. See Q319 for unused closure capture detection. See Q311 for quality checks.","ek9Example":"defines module qa.gettingStarted.unusedCapture\n\n  defines function\n\n    mathOperation() as pure abstract\n      ->\n        x as Float\n        y as Float\n      <- result as Float?\n\n  defines program\n\n    UnusedCaptureDemo()\n      stdout <- Stdout()\n\n      //THE FIX: capture ONLY the variables the body actually uses.\n      //'factor' is read inside the body, so it belongs in the capture list.\n      factor <- 10.0\n      scaled <- (factor) is mathOperation as pure function\n        result:=? x * factor + y\n\n      stdout.println(`captured: ${scaled(3.0, 1.0)}`)\n\n      //A value that is NOT needed by the body must NOT appear in the capture\n      //list. Here 'offset' is used, so it is captured; nothing surplus is.\n      offset <- 5.0\n      biased <- (offset) is mathOperation as pure (result:=? x + y + offset)\n      stdout.println(`biased: ${biased(1.0, 2.0)}`)","migrationContext":"Java/Kotlin/JavaScript/Python: capture is implicit and unchecked - a lambda silently closes over any enclosing variable it references, and there is no notion of an 'unused capture' because nothing is captured unless referenced. The dual risk is accidental capture of large objects (memory leaks) with no compiler signal. EK9: capture is an EXPLICIT list, so an entry that the body never uses is unambiguously a mistake and is rejected at compile time via E11018, keeping the declared dependency set exactly equal to the used set.","keywords":["E11018","capture","closure","dependency","dynamic","function","quality","unused"],"primaryTopics":["unused captured variable","E11018","closure capture"],"typicalErrors":[{"error":"E11018","correct":"scaled <- (factor) is mathOperation as pure function\n        result:=? x * factor + y","incorrect":"scaled <- (factor, stdout) is mathOperation as pure function\n        result:=? x * factor + y","explanation":"The capture list includes 'unused', but the body only reads 'factor', 'x' and 'y'. An explicitly captured variable that the function body never references is dead weight - an unnecessary by-value copy and a false dependency - so EK9 raises E11018. Remove the unused entry from the capture list, keeping only the variables the body actually uses. See ek9 -h E11018 for details."}],"companions":[]}
{"id":1339,"category":"Code Quality","question":"Why does EK9 reject a function whose complexity and size are both individually within limits?","url":"https://ek9.io/qa/QA1339.html","alternatePhrasings":["What triggers E11020 COMBINED_COMPLEXITY_SIZE?","Why does EK9 fail a function that passes E11010 and E11012 separately?","How do I fix a function that is both moderately complex and moderately large?"],"answer":"EK9 checks complexity and statement count not only against their individual ceilings (E11010 for complexity, E11012 for statement count) but also against their COMBINATION. The combined score is (complexity / 45) x (statements / 150 for a function); if it exceeds 0.50 the compiler raises E11020 even though neither metric alone has broken its limit. A function at 73% complexity and 87% size scores 0.64 and fails. The fix is to decompose the one large branchy function into several small focused functions, each with low complexity and few statements, then compose them. This drives both ratios down so the product falls under 0.50. The research basis is NASA SATC work showing that modules which are both moderately complex AND moderately large have the lowest reliability.\n\nSee Q311 for the full list of quality checks and Q312 for how complexity is measured.","ek9Example":"defines module qa.quality.combined.complexity.size\n\n  defines constant\n\n    CODE_NEW <- 0\n    CODE_PAID <- 1\n    CODE_SHIPPED <- 2\n\n  defines function\n\n    //THE FIX: small, focused functions keep BOTH complexity and statement\n    //count low, so the combined (complexity/45) x (statements/150) score\n    //stays well under the 0.50 threshold and E11020 never fires.\n\n    classifyCode() as pure\n      -> code as Integer\n      <- level as String: \"unknown\"\n      if code == CODE_NEW\n        level: \"new\"\n      else if code == CODE_PAID\n        level: \"paid\"\n      else if code == CODE_SHIPPED\n        level: \"shipped\"\n\n    describeCode() as pure\n      -> level as String\n      <- description as String: \"no description\"\n      if level == \"new\"\n        description: \"awaiting payment\"\n      else if level == \"paid\"\n        description: \"ready to ship\"\n      else if level == \"shipped\"\n        description: \"in transit\"\n\n    formatReport() as pure\n      ->\n        level as String\n        description as String\n      <-\n        report as String: `${level}: ${description}`\n\n    //Composition function: trivial complexity, few statements.\n    analyseOrder() as pure\n      -> code as Integer\n      <- report as String: \"\"\n      level <- classifyCode(code)\n      description <- describeCode(level)\n      report: formatReport(level, description)\n\n  defines program\n\n    CombinedComplexitySizeDemo()\n      stdout <- Stdout()\n      codes <- [CODE_NEW, CODE_PAID, CODE_SHIPPED]\n      for code in codes\n        stdout.println(analyseOrder(code))","migrationContext":"Java/Kotlin/Swift: the compiler never analyses the product of complexity and size; a method that is both moderately branchy and moderately long compiles silently and is only flagged later by optional tools (SonarQube, Detekt, SwiftLint) if a custom rule is configured, and even then as an advisory warning that can be suppressed. EK9 enforces the combined complexity-size threshold at compile time as a hard error (E11020), so the over-grown function simply will not build until it is decomposed.","keywords":["E11010","E11012","E11020","combined","complexity","decompose","maintainability","quality","size","statements"],"primaryTopics":["combined complexity and size","E11020","function decomposition"],"typicalErrors":[],"companions":[]}
{"id":1340,"category":"Code Quality","question":"Why does EK9 suggest promoting an injected type to a field when it appears in three or more methods (E11041)?","url":"https://ek9.io/qa/QA1340.html","alternatePhrasings":["What triggers E11041 INJECTION_PROMOTE_TO_FACADE?","Why does EK9 flag the same '!' injected type used in 3+ methods of one component?","How do I fix a component that re-injects the same service in every method?"],"answer":"When the SAME injected type is requested via the method-local '!' suffix inside 3 or more methods of a single component, E11041 (INJECTION_PROMOTE_TO_FACADE) advises promoting that dependency to a component field. Repeating the local injection in every method is a smell: the dependency is clearly central to the component's purpose, so it belongs as a single declared field rather than being re-resolved on every call.\n\nFix: declare the dependency once as a field with '!' at component scope, then have each method use that field. If a component accumulates many such central dependencies, group the related collaborators behind one facade component and inject the facade instead.\n\nSee Q960 for the per-component injection-field limit (E11040). See Q311 for quality checks.","ek9Example":"defines module qa.quality.injection.promote\n\n  defines component\n\n    //Abstract contract for the central dependency.\n    AuditService as abstract\n      record() as abstract\n        -> message as String\n      default operator ?\n\n    ConsoleAudit extends AuditService\n      override record()\n        -> message as String\n        Stdout().println(\"[AUDIT] \" + message)\n      default operator ?\n\n    //THE FIX: the AuditService is central to this component, so it is\n    //declared ONCE as a field with '!' rather than re-injected locally\n    //inside every method. Each method simply uses the field.\n    OrderProcessor\n\n      audit as AuditService!\n\n      submit()\n        -> label as String\n        audit.record(`submit ${label}`)\n\n      cancel()\n        -> label as String\n        audit.record(`cancel ${label}`)\n\n      refund()\n        -> label as String\n        audit.record(`refund ${label}`)\n\n      default operator ?\n\n  defines application\n\n    OrderApp\n      register ConsoleAudit() as AuditService\n\n  defines program\n\n    //CORRECT: the central dependency is a single field, used by all methods.\n    PromoteInjectionDemo() with application of OrderApp\n      stdout <- Stdout()\n      processor <- OrderProcessor()\n      processor.submit(\"order-1\")\n      stdout.println(\"Central dependency promoted to a single field\")","migrationContext":"Java Spring: @Autowired on a field is conventional, but nothing stops repeated method-parameter injection or ObjectProvider lookups in every method; detected only by SonarQube/ArchUnit if configured. C#/.NET: Seemann recommends constructor injection of central dependencies but it is advisory. Go Wire: no per-method guidance. EK9: the compiler observes the repetition of a single injected type across 3+ methods and steers you to promote it to a field (E11041).","keywords":["E11041","component","dependency","facade","field","injection","method","promote","quality"],"primaryTopics":["promote injection to field","E11041","INJECTION_PROMOTE_TO_FACADE"],"typicalErrors":[{"error":"E11041","correct":"      audit as AuditService!\n\n      submit()\n        -> label as String\n        audit.record(`submit ${label}`)","incorrect":"submit()\n        -> label as String\n        audit as AuditService!\n        audit.record(label)\n\n      cancel()\n        -> label as String\n        audit as AuditService!\n        audit.record(label)\n\n      refund()\n        -> label as String\n        audit as AuditService!\n        audit.record(label)","explanation":"The same AuditService type is method-local injected ('!') inside three separate methods, signalling that it is central to the component. Promote it to a single component field declared once with '!', then have every method use that field. If many central dependencies pile up, group them behind one facade component. See ek9 -h E11041 for details."}],"companions":[]}
{"id":1341,"category":"Code Quality","question":"Why does EK9 require a dedicated constants file once a module has many constants?","url":"https://ek9.io/qa/QA1341.html","alternatePhrasings":["What triggers E11067 in EK9?","Why must 10+ module constants live in a single file?","How do I consolidate a large constant vocabulary spread across files?"],"answer":"EK9 raises E11067 when a multi-file module has 10 or more constants spread across 2 or more files. This volume of constants across multiple files signals that the module's vocabulary has grown large enough to deserve a single dedicated constants file.\n\nWHY CONSOLIDATION MATTERS\nA large, scattered constant vocabulary is a maintenance burden: no developer can see the full set of fixed values without opening multiple files, duplicates and near-duplicates hide easily, and the intent of 'this is where constants live' is lost.\n\nTHE FIX\nCreate one dedicated constants file for the module and move every 'defines constant' block into it. Group related constants together with doc comments. If the count is very high, ask whether the module is doing too much.\n\nThis example shows the consolidated end-state: all 12 module constants live in a single file, organised by concern, so the multi-file/high-volume condition never arises.\n\nSee Q863 for the related 3+ file consolidation check (E11066). See Q695 for named constants. See Q311 for quality checks.","ek9Example":"defines module qa.quality.constants.volume\n\n  defines constant\n\n    <?-\n      Retry and timeout configuration.\n      Used by network and service components.\n    -?>\n    MAX_RETRIES <- 3\n    RETRY_DELAY_MS <- 500\n    CONNECTION_TIMEOUT_MS <- 5000\n    READ_TIMEOUT_MS <- 8000\n\n    <?-\n      Endpoint configuration.\n      Centralised so a host change touches one place only.\n    -?>\n    BASE_URL <- \"https://api.example.com\"\n    HEALTH_PATH <- \"/health\"\n    METRICS_PATH <- \"/metrics\"\n\n    <?-\n      Reporting and formatting constants.\n      Used by the output and report functions.\n    -?>\n    SEPARATOR_LINE <- \"----------------------------------------\"\n    INDENT_SPACES <- \"    \"\n    PAGE_SIZE <- 50\n    MAX_PAGES <- 20\n    REPORT_TITLE <- \"Service Status Report\"\n\n  defines function\n\n    <?-\n      Build a single report line using the shared formatting constants.\n    -?>\n    formatReportLine() as pure\n      -> content as String\n      <- rtn as String: INDENT_SPACES + content\n\n    <?-\n      Compute how many items a full report could contain\n      using the consolidated paging constants.\n    -?>\n    maxReportItems() as pure\n      <- rtn as Integer: PAGE_SIZE * MAX_PAGES\n\n  defines program\n\n    ConstantsVolumeDemo()\n      stdout <- Stdout()\n\n      stdout.println(SEPARATOR_LINE)\n      stdout.println(formatReportLine(REPORT_TITLE))\n      stdout.println(SEPARATOR_LINE)\n\n      stdout.println(formatReportLine(`Base URL: ${BASE_URL}${HEALTH_PATH}`))\n      stdout.println(formatReportLine(`Max retries: ${MAX_RETRIES}`))\n      stdout.println(formatReportLine(`Connect timeout: ${CONNECTION_TIMEOUT_MS}ms`))\n      stdout.println(formatReportLine(`Read timeout: ${READ_TIMEOUT_MS}ms`))\n      stdout.println(formatReportLine(`Metrics at: ${METRICS_PATH}`))\n      stdout.println(formatReportLine(`Retry delay: ${RETRY_DELAY_MS}ms`))\n      stdout.println(formatReportLine(`Max report items: ${maxReportItems()}`))\n\n      stdout.println(SEPARATOR_LINE)","migrationContext":"Java/Kotlin/Go: there is no compiler enforcement of how constants are organised across files; teams rely on convention (a Constants class, a constants.py, a const block) and on reviewers spotting sprawl. EK9 enforces it at compile time: once a module crosses 2+ files AND 10+ constants, E11067 fails the build until the constants are consolidated into one dedicated file.","keywords":["E11067","consolidate","constant","file","module","organization","quality","scattered","vocabulary","volume"],"primaryTopics":["constant consolidation","E11067","dedicated constants file"],"typicalErrors":[],"companions":[]}
{"id":1342,"category":"Code Quality","question":"Why does EK9 reject a deeply nested higher-order call chain like getMaker(x)(y)(z)?","url":"https://ek9.io/qa/QA1342.html","alternatePhrasings":["What triggers E11069 EXCESSIVE_CALL_CHAIN?","Why can't I chain more than one functional call in EK9?","How do I fix a call applied to the result of a call applied to a call?"],"answer":"EK9 allows single-level functional chaining (e.g. getTransformer(\"upper\")(\"text\") - a delegate-returning function whose delegate is then invoked) but rejects deeper nesting with E11069. A two-level chain such as getMaker(\"x\")(\"seed\")(\"text\") forces inside-out mental evaluation: you must work out what getMaker(\"x\") returns, then what calling that with (\"seed\") returns, then call THAT with (\"text\"). Each level hides a type transition. The fix is to extract each intermediate result into a named, typed local variable so every step is independently readable, inspectable, and reviewable. Single-level chaining remains valid and idiomatic.\n\nSee Q311 for quality checks.","ek9Example":"defines module qa.quality.call.chain\n\n  defines function\n\n    Transformer() as pure abstract\n      -> input as String\n      <- output as String?\n\n    UpperTransformer() extends Transformer as pure\n      -> input as String\n      <- output as String: input.upperCase()\n\n    Maker() as abstract\n      -> seed as String\n      <- transformer as Transformer?\n\n    SimpleMaker() extends Maker\n      -> seed as String\n      <- transformer as Transformer: UpperTransformer\n\n    getMaker()\n      -> kind as String\n      <- maker as Maker: SimpleMaker\n\n      if kind == \"simple\"\n        maker: SimpleMaker\n\n    //THE FIX: extract each intermediate result into a named, typed local.\n    //getMaker returns a Maker, the Maker returns a Transformer, the\n    //Transformer returns a String. Each step is now independently clear,\n    //instead of one inside-out chain getMaker(\"simple\")(\"seed\")(\"text\").\n    buildResult()\n      <- result as String?\n      maker <- getMaker(\"simple\")\n      transformer <- maker(\"seed\")\n      result: transformer(\"text\")\n\n  defines program\n\n    ExcessiveCallChainDemo()\n      stdout <- Stdout()\n      result <- buildResult()\n      stdout.println(`Result is ${result}`)","migrationContext":"JavaScript/curried FP: f(a)(b)(c)(d) is unlimited and unchecked, leaving the reader to trace types inside-out. Haskell/Scala: deep currying is idiomatic but relies on a type-aware editor to reveal intermediate types. C: the same cognitive problem as nested pointer dereferences (*(**fp)(x))(y). EK9: compile-time error (E11069) at one level beyond single chaining, forcing extraction into named intermediate variables that document each type.","keywords":["E11069","call","chain","delegate","extract","higher-order","intermediate","nested","quality"],"primaryTopics":["excessive call chain","E11069","intermediate variables"],"typicalErrors":[{"error":"E11069","correct":"maker <- getMaker(\"simple\")\n      transformer <- maker(\"seed\")\n      result: transformer(\"text\")","incorrect":"result: getMaker(\"simple\")(\"seed\")(\"text\")","explanation":"The expression is a call applied to the result of a call applied to the result of a call - two levels of nesting beyond a plain call. This forces inside-out evaluation and hides each type transition. Extract each step into a named typed local (maker, transformer) so every line documents its type and the final call is single-level. Single-level chaining such as getTransformer(\"upper\")(\"text\") is fine. See ek9 -h E11069 for details."}],"companions":[]}
{"id":1343,"category":"Functions and Methods","question":"Why does EK9 reject a named argument whose name does not match the parameter?","url":"https://ek9.io/qa/QA1343.html","alternatePhrasings":["What triggers E06250 when calling a function with named parameters?","Why must named call arguments use the exact declared parameter names in EK9?","How do I fix 'the order and naming of arguments must match parameters'?"],"answer":"EK9 lets you pass arguments by name using 'name: value' syntax, which makes call sites self-documenting. When you do, every name must EXACTLY match a declared parameter name (case-sensitive) and stay in declaration order. Passing a name that does not exist on the signature - a typo, a guessed name, or a name left stale after a refactor - raises E06250.\n\nThe fix is to read the function or method signature and use the real parameter names. In the example, makeRange declares 'low' and 'high'; calling makeRange(low: 2, high: 10) compiles, while makeRange(low: 2, max: 10) fails because there is no parameter called 'max'.\n\nSee Q597 for function parameters. See Q942 for passing functions as parameters.","ek9Example":"defines module qa.functionsAndMethods.namedarguments\n\n  defines function\n\n    //Two parameters with clear, documented names.\n    makeRange() as pure\n      ->\n        low as Integer\n        high as Integer\n      <- rtn as Integer: high - low\n\n  defines program\n\n    NamedArgumentDemo()\n      stdout <- Stdout()\n\n      //CORRECT: named arguments match the declared parameter names exactly,\n      //in declaration order. Self-documenting and compile-checked.\n      span <- makeRange(low: 2, high: 10)\n      stdout.println(`Span via named args: ${span}`)\n\n      //Positional calls remain valid too when names are obvious.\n      other <- makeRange(0, 5)\n      stdout.println(`Span via positional args: ${other}`)","migrationContext":"Python: keyword arguments are checked at runtime - a wrong keyword raises TypeError only when the call executes. Kotlin/C#: named arguments are compile-checked like EK9, a wrong name fails to compile. Java: has no named arguments at all, so the whole class of name-mismatch bugs is replaced by silent positional mistakes. EK9: named arguments are compile-time validated (E06250) and must match the declared names exactly and stay in order.","keywords":["E06250","argument","call","function","match","named","order","parameter","signature"],"primaryTopics":["named arguments","E06250","parameter name matching"],"typicalErrors":[{"error":"E06250","correct":"span <- makeRange(low: 2, high: 10)","incorrect":"span <- makeRange(low: 2, max: 10)","explanation":"makeRange declares parameters 'low' and 'high', but the call uses 'max' as the second name. Named arguments must match the declared parameter names exactly and stay in declaration order, so the unknown name 'max' raises E06250. Use the real parameter name 'high'. See ek9 -h E06250 for details."}],"companions":[]}
{"id":1344,"category":"Dispatcher Validation","question":"Why does EK9 report E06320 when a dispatcher handler has a different number of parameters from the entry?","url":"https://ek9.io/qa/QA1344.html","alternatePhrasings":["What triggers E06320 invalid number of parameters on a dispatcher?","Why must every dispatcher handler have the same parameter count as the entry?","How do I fix a dispatcher handler that takes too many parameters?"],"answer":"A dispatcher distributes a single call across handler methods of the same name, selecting the handler by the runtime type(s) of the argument(s). Every handler must therefore have the SAME number of parameters as the dispatcher entry method. If the entry takes one parameter but a handler takes two (or zero), the compiler cannot line the arguments up at the dispatch point and reports E06320 (invalid number of parameters). The dispatcher entry itself must also declare one or two parameters; zero or more than two is rejected with the same error.\n\nFIX: give every handler exactly the same parameter count as the entry. If a handler genuinely needs extra data, pass it into the dispatcher entry too (a two-parameter dispatcher), or carry it on the argument object, so all overloads stay aligned.\n\nSee Q619 for two-parameter dispatch and Q105 for method dispatch.","ek9Example":"defines module qa.dispatcher.handler.parametercount\n\n  defines class\n\n    Shape as abstract\n      area() as pure abstract\n        <- rtn as Float?\n      default operator ?\n\n    Circle extends Shape\n      override area() as pure\n        <- rtn as Float: 3.14\n      default operator ?\n\n    Square extends Shape\n      override area() as pure\n        <- rtn as Float: 4.0\n      default operator ?\n\n    //CORRECT: the dispatcher entry takes one parameter, and EVERY handler\n    //also takes exactly one parameter of the same arity. The handler is\n    //selected at run time by the actual type of the single argument.\n    AreaReporter\n      report() as pure dispatcher\n        -> shape as Shape\n        <- rtn as String: `Shape area ${shape.area()}`\n\n      report() as pure\n        -> shape as Circle\n        <- rtn as String: `Circle area ${shape.area()}`\n\n      report() as pure\n        -> shape as Square\n        <- rtn as String: `Square area ${shape.area()}`\n\n  defines program\n\n    DispatcherParameterCountDemo()\n      stdout <- Stdout()\n      reporter <- AreaReporter()\n      stdout.println(reporter.report(Circle()))\n      stdout.println(reporter.report(Square()))","migrationContext":"Java/Kotlin: overloaded methods may freely vary their parameter counts; the compiler picks an overload at compile time by static type, so arity differences are normal. EK9 dispatchers select a handler at run time by the argument's actual type, so all handlers must share the entry's arity - a difference is a compile-time error (E06320) rather than a silent overload.","keywords":["E06320","arity","count","dispatch","dispatcher","entry","handler","invalid number of parameters","overload","parameter"],"primaryTopics":["dispatcher parameter count","E06320","dispatcher handler arity"],"typicalErrors":[{"error":"E06320","correct":"report() as pure\n        -> shape as Circle","incorrect":"report() as pure\n        ->\n          shape as Circle\n          unit as String","explanation":"The dispatcher entry report takes one parameter, so every handler must also take exactly one parameter. A two-parameter handler cannot be aligned with the single-argument dispatch site, so the compiler reports E06320. Keep all handlers at the entry's arity (or make the entry two-parameter too). See ek9 -h E06320 for details."}],"companions":[]}
{"id":1345,"category":"Streams and Pipelines","question":"Why does my stream 'head' fail when I give it a Date variable instead of an Integer or a function?","url":"https://ek9.io/qa/QA1345.html","alternatePhrasings":["What does E07870 (Integer or function required) mean for head/tail/skip?","Can I pass any variable as the count for head in an EK9 stream?","Why must the head/tail/skip count be an Integer value or a function?"],"answer":"The count for a stream limiting stage (head, tail, skip) must be either an Integer value OR a zero-argument function/function-delegate. If you pass a plain variable that is neither an Integer nor a function (for example a Date), the compiler raises E07870 because it cannot derive an item count from that value.\n\nVALID COUNTS\n- Integer literal: cat items | head 2 > stdout\n- Integer variable: cat items | head limit > stdout\n- Function returning Integer: cat items | head howMany > stdout\n\nNote a related but distinct check: if you DO pass a function but its return type is not Integer, you get E07550 (must return Integer) instead. E07870 is specifically the 'this is neither an Integer nor a function' case.\n\nSee Q812 for the full catalogue of stream pipeline errors.","ek9Example":"defines module qa.streams.headcountfunction\n\n  defines function\n\n    //Valid count supplier: zero arguments, returns Integer.\n    howMany()\n      <- rtn as Integer: 2\n\n  defines program\n\n    StreamHeadCountDemo()\n      stdout <- Stdout()\n\n      items <- [\"alpha\", \"beta\", \"gamma\", \"delta\"]\n\n      // === head with an Integer literal ===\n      stdout.println(\"Head 2 (literal):\")\n      cat items | head 2 > stdout\n\n      // === head with a function returning Integer ===\n      stdout.println(\"Head howMany (function returns Integer):\")\n      cat items | head howMany > stdout","migrationContext":"Java: Stream.limit(long) only accepts a primitive long, so there is no delegate form at all. Python: itertools.islice takes a plain int. Rust: .take(usize) takes an integer directly. EK9: head/tail/skip accept either an Integer value or a function/delegate that returns Integer, validated at compile time via E07870.","keywords":["E07870","Integer","count","delegate","function","head","skip","stream","tail"],"primaryTopics":["stream head count","E07870","Integer or function required"],"typicalErrors":[{"error":"E07870","correct":"cat items | head howMany > stdout","incorrect":"cat items | head items > stdout","explanation":"head/tail/skip need an Integer value or a function/function-delegate. 'howMany' is a zero-argument function returning Integer so it is a valid count supplier, but 'dueDate' is a Date variable, which is neither an Integer nor a function, so the compiler raises E07870. (A function that returns a non-Integer would instead raise E07550.) See ek9 -h E07870 for details."}],"companions":[]}
{"id":1346,"category":"Generics","question":"Why does E07175 fire on my generic class with a 'T?' field and a default constructor?","url":"https://ek9.io/qa/QA1346.html","alternatePhrasings":["My generic 'default Container() as pure' is rejected — why?","How do I stop a generic's optional field being null?","E07175 default constructor must be private when uninitialised properties exist — on a generic"],"answer":"A generic field declared 'item as T?' is uninitialised at declaration. If the only no-argument constructor is the compiler-generated 'default Container()', that constructor leaves 'item' NULL — and any later use (for example the synthetic '?' operator reading 'item?') would dereference null. This is the billion-dollar mistake, so EK9 rejects it at compile time with E07175.\n\n  Error : E07175: 'Container' ...: default constructor must be 'private' when uninitialised properties exist\n\nTHE IDIOMATIC FIX: give the field an unset T()\nReplace 'default Container()' with a real no-arg constructor that first-initialises the field to an unset T(). The field is then present-but-unset (never null):\n\n  Container of type T\n    item as T?\n    Container() as pure\n      item :=? T()                 // present-but-unset, never null\n    Container() as pure\n      -> initial as T\n      item :=? initial\n\nWHY ':=?' (not ':=' or ':')\nIn a pure constructor ':=' is forbidden (E08100). The guarded ':=?' assigns only if the field is unset, which is exactly 'first-initialise to an unset value'.\n\nIF T() IS NOT AVAILABLE\nWhen T may be abstract, may hold a function, or has no public no-arg constructor, you cannot call T(). Then declare 'default private Container()' instead - external code cannot create an empty one, and callers must use the inferred constructor with a value.\n\nSee Q1347 for creating empty instances. See Q1348 for the T() limits. See Q645 for the private no-arg escape hatch.","ek9Example":"defines module qa.genericsdeep.optionalfieldnullsafety\n\n  defines class\n\n    Container of type T\n      item as T?\n\n      Container() as pure\n        item :=? T()\n\n      Container() as pure\n        -> initial as T\n        item :=? initial\n\n      hasItem() as pure\n        <- rtn as Boolean: item?\n\n      default operator ?\n\n  defines program\n\n    OptionalFieldDemo()\n      stdout <- Stdout()\n\n      emptyOne <- Container() of String\n      stdout.println(`emptyOne hasItem: ${emptyOne.hasItem()}`)\n\n      filled <- Container(\"hello\")\n      stdout.println(`filled hasItem: ${filled.hasItem()}`)","migrationContext":"Java/Kotlin: a generic field T is null until assigned; reading it risks NullPointerException at runtime. Rust: Option<T> forces an explicit None. EK9: a 'T?' field must be made present-but-unset (item :=? T()) or the type must forbid empty construction (default private) - the null is eliminated at compile time, not deferred to runtime.","keywords":["E07175","T?","constructor","default","field","generic","guarded-assignment","null","optional","present-but-unset","uninitialised"],"primaryTopics":[],"typicalErrors":[{"error":"E07175","correct":"Container() as pure\n        item :=? T()","incorrect":"default Container() as pure","explanation":"A generic 'default Container()' leaves a 'T?' field null. Give the field an unset T() in the no-arg constructor (item :=? T()), or declare the default private. See ek9 -h E07175 for details."}],"companions":[]}
{"id":1347,"category":"Generics","question":"When must a generic class use 'default private' for its no-argument constructor?","url":"https://ek9.io/qa/QA1347.html","alternatePhrasings":["I cannot call T() in my generic - now what?","My generic holds an abstract type or a function and item :=? T() fails","How do I write a generic with an optional field that cannot be defaulted?"],"answer":"The idiomatic way to keep a generic's 'T?' field non-null is to give it an unset T() in the no-arg constructor ('item :=? T()'). But T() is not always available - it requires the concrete type used to parameterise the generic to have a public no-argument constructor. T() is NOT available when:\n1. T may be ABSTRACT - an abstract type cannot be constructed (E10030).\n2. T may hold a FUNCTION - a function cannot be constructed with T() (E06110).\n3. T may have NO public no-arg constructor - for example a type whose own default is 'default private' (E10031).\n4. T's no-arg constructor may be NON-pure while the generic calls 'T()' in a PURE constructor - a pure 'T()' demands a pure no-arg constructor on the parameterising type (E10032).\n\nThese constructor requirements (existence, public access, purity) are properties of the CONCRETE type, so they are only checked at parameterisation - not when the generic is defined.\n\nIn these cases, declare the no-arg default PRIVATE:\n\n  Holder of type T\n    item as T?\n    default private Holder() as pure          // escape hatch - no T() needed\n    Holder() as pure\n      -> initial as T\n      item :=? initial\n\nWHAT 'default private' GIVES YOU\nExternal code CANNOT create an empty Holder (the no-arg constructor is private), so it can never observe a null field. Callers must use the inferred constructor with a real value. The inferred (parameterised) constructor stays public so type inference still works.\n\nDECISION RULE\n- T always has a public no-arg constructor (String, Integer, your own value types): use 'item :=? T()' so empty construction is allowed and the field is present-but-unset.\n- T might not (abstract bound, function, or no-arg-private types): use 'default private' and construct only with a value.\n\nSee Q1346 for the E07175 error. See Q1348 for the two safe shapes. See Q645 for the constructor access rule.","ek9Example":"defines module qa.genericsdeep.defaultprivateescapehatch\n\n  defines class\n\n    Holder of type T\n      item as T?\n\n      default private Holder() as pure\n\n      Holder() as pure\n        -> initial as T\n        item :=? initial\n\n      get() as pure\n        <- rtn as T: item\n\n      default operator ?\n\n  defines program\n\n    EscapeHatchDemo()\n      stdout <- Stdout()\n\n      //Cannot do 'Holder() of String' here - the no-arg default is private.\n      filled <- Holder(\"present\")\n      stdout.println(`filled set: ${filled?}`)","migrationContext":"Java: a generic with an abstract or functional T simply leaves the field null and you hope no one reads it. EK9: you must choose - either guarantee an unset value with T(), or forbid empty construction with 'default private'. Either way the null is designed out at compile time.","keywords":["E06110","E07175","E10030","E10031","E10032","T","abstract","constructor","default","escape-hatch","function","generic","no-arg","private","pure"],"primaryTopics":[],"typicalErrors":[{"error":"E07175","correct":"default private Holder() as pure","incorrect":"default Holder() as pure","explanation":"A generic with an uninitialised 'T?' field must make its no-arg default constructor 'private' (E07175); a public 'default' no-arg constructor would let external code create an empty instance whose field is never set. Keep it 'default private' so callers must supply a value via the inferred constructor. See ek9 -h E07175 for details."}],"companions":[]}
{"id":1348,"category":"Generics","question":"What are the two safe ways to write a generic class that has an optional 'T?' field?","url":"https://ek9.io/qa/QA1348.html","alternatePhrasings":["How should a generic optional field be initialised in EK9?","Default-value generic field versus no-empty-construction generic field","Generic class with a nullable field done safely"],"answer":"A generic 'T?' field is uninitialised at declaration, so EK9 forces you into one of exactly two safe shapes - both eliminate the null at compile time.\n\nSHAPE 1: PRESENT-BUT-UNSET (public no-arg, give the field a T())\nUse when T is guaranteed to have a public no-argument constructor (String, Integer, your own value types). Empty construction is allowed; the field is an unset T, never null:\n\n  Box of type T\n    content as T?\n    Box() as pure\n      content :=? T()\n    Box() as pure\n      -> initial as T\n      content :=? initial\n\n  empty <- Box() of String     // allowed; content is unset, '?' is false\n\nSHAPE 2: NO EMPTY CONSTRUCTION (default private)\nUse when T() is not available (T may be abstract, hold a function, or lack a public no-arg constructor). The no-arg default is private, so no empty instance can be created; callers must pass a value:\n\n  Box of type T\n    content as T?\n    default private Box() as pure\n    Box() as pure\n      -> initial as T\n      content :=? initial\n\n  filled <- Box(someValue)     // only way to construct - always has a value\n\nWHAT IS NOT ALLOWED\nA public 'default Box()' with a 'T?' field is E07175 - it would leave the field null. Pick shape 1 or shape 2.\n\nThe difference is a deliberate design choice: shape 1 says 'an empty one is meaningful (and unset)', shape 2 says 'there is no meaningful empty one'.\n\nSee Q1346 for the E07175 error. See Q1347 for the private escape hatch. See Q643 for the two-constructor requirement.","ek9Example":"defines module qa.genericsdeep.twosafeshapes\n\n  defines class\n\n    //Shape 1: present-but-unset - an empty one is meaningful\n    Box of type T\n      content as T?\n\n      Box() as pure\n        content :=? T()\n\n      Box() as pure\n        -> initial as T\n        content :=? initial\n\n      default operator ?\n\n    //Shape 2: no empty construction - there is no meaningful empty one\n    Required of type T\n      content as T?\n\n      default private Required() as pure\n\n      Required() as pure\n        -> initial as T\n        content :=? initial\n\n      default operator ?\n\n  defines program\n\n    TwoSafeShapesDemo()\n      stdout <- Stdout()\n\n      emptyBox <- Box() of String\n      stdout.println(`empty box set: ${emptyBox?}`)\n\n      filledBox <- Box(\"value\")\n      stdout.println(`filled box set: ${filledBox?}`)\n\n      onlyWithValue <- Required(\"must have\")\n      stdout.println(`required set: ${onlyWithValue?}`)","migrationContext":"Java/Kotlin: nothing forces a choice - the generic field is null until set and the risk is silent. Rust: Option<T> is always explicit. EK9: the compiler forces shape 1 (present-but-unset via T()) or shape 2 (no empty construction via default private), so a generic optional field is never a hidden null.","keywords":["T?","default","design","field","generic","null-safety","optional","present-but-unset","private","shapes","two"],"primaryTopics":[],"typicalErrors":[{"error":"E07175","correct":"Box() as pure\n        content :=? T()","incorrect":"default Box() as pure","explanation":"A public default constructor with a 'T?' field leaves it null (E07175). Choose shape 1 (content :=? T()) or shape 2 (default private). See ek9 -h E07175 for details."}],"companions":[]}
{"id":1349,"category":"Concurrency","question":"I got E08250 THIS_ESCAPES_CONSTRUCTOR — why can't I pass 'this' out of a constructor?","url":"https://ek9.io/qa/QA1349.html","alternatePhrasings":["Why does EK9 reject passing this in a constructor?","How to register an object during construction in EK9?","this escapes constructor error E08250","Publishing this from a constructor body EK9"],"answer":"E08250 fires when a constructor passes `this` as an argument to another method or function before construction has finished. The object is only partially built at that point — its fields may still be unset — so letting a reference escape lets other code (or another thread) observe a half-initialised object. This is the classic 'unsafe publication' bug that causes intermittent NullPointer-style failures and data races.\n\nWHAT THE ERROR MEANS\nInside a constructor body, `this` is still being assembled. Handing it to `reg.register(this)`, storing it in a collection owned by someone else, or capturing it into a function that outlives the constructor all let the not-yet-finished object be used. EK9 rejects any `this` argument inside a constructor body.\n\nHOW TO FIX\nComplete construction first, then register. Move the escaping call OUT of the constructor into a separate step the caller performs after the object exists, or use a factory function that constructs the object fully and only then registers it.\n\n  publisher <- Publisher(\"news\")   //fully constructed\n  registry.register(publisher)      //register afterwards — safe\n\nWHY EK9 DETECTS THIS\nUnsafe publication is invisible in most languages — Java, C#, and C++ all let `this` escape a constructor with no warning, and the resulting races only surface under load. EK9 forbids it structurally so a reference to a half-built object can never be observed.","ek9Example":"defines module qa.concurrency.this.escapes.constructor\n\n  defines class\n\n    Registry\n      items as List of Publisher: List() of Publisher\n      default Registry()\n      register()\n        -> p as Publisher\n        items += p\n      count() as pure\n        <- rtn as Integer: length items\n      default operator ?\n\n    Publisher\n      name as String: String()\n      Publisher()\n        ->\n          n as String\n          reg as Registry\n        this.name: n\n        stdout <- Stdout()\n        stdout.println(`registry size ${reg.count()}`)\n      default operator ?","migrationContext":"Java/C#/C++: `this` may escape a constructor freely; escape analysis tools flag only some cases and the resulting races are load-dependent. EK9: any `this` argument inside a constructor body is a compile-time error, eliminating unsafe publication entirely.","keywords":["E08250","constructor","escapes","half-built","initialisation","publication","race","register","safe","this","unsafe"],"primaryTopics":["construction safety","unsafe publication"],"typicalErrors":[{"error":"E08250","correct":"stdout <- Stdout()\n        stdout.println(`registry size ${reg.count()}`)","incorrect":"reg.register(this)","explanation":"The constructor passes `this` to `reg.register(this)` before the Publisher is fully built, letting a half-initialised object be observed (and, across threads, raced on). The fix is to finish construction and register afterwards from the caller, or via a factory. See ek9 -h E08250."}],"companions":[]}
{"id":1350,"category":"Concurrency","question":"I got E08251 SHARED_STATE_MUTATION_OUTSIDE_LOCK — why can't my async worker write shared state?","url":"https://ek9.io/qa/QA1350.html","alternatePhrasings":["Why must I write shared state inside a lock in EK9?","async worker mutates shared field error E08251","How to mutate shared data safely across an async boundary","captured write outside MutexLock EK9"],"answer":"E08251 fires when code mutates shared state that crosses a thread boundary without doing so inside a MutexLock.enter(...) body. The most common trigger is capturing a shared object into a `| async` worker and writing one of its fields there — the worker runs on a different thread, so the write races with anyone else touching that object.\n\nWHAT THE ERROR MEANS\nA `| async` stage dispatches each element to a thread-pool worker. If the worker captures a shared record and writes its field, that write happens off the caller's thread with no synchronisation. EK9 requires every mutation of shared state to happen inside a MutexKey body guarded by a MutexLock.\n\nHOW TO FIX\nEither keep the mutation on the same thread (read-only in the worker, aggregate afterwards), or route the mutation through a MutexLock so the write is serialised. Reading shared state in a worker is fine — only mutation is rejected.\n\n  //worker only READS; the write happens under a lock elsewhere\n  worker <- (shared) is asyncWorkerFn as function\n    rtn: shared.total > 0\n\nWHY EK9 DETECTS THIS\nData races on shared mutable state are among the hardest bugs to reproduce. EK9 makes the thread boundary visible (`| async`) and tracks which state the worker captures, refusing any unsynchronised mutation.","ek9Example":"defines module qa.concurrency.shared.state.outside.lock\n\n  defines record\n    Tally\n      total <- 0\n      default operator ?\n\n  defines function\n\n    asyncWorkerFn() as abstract\n      <- rtn as Boolean?\n\n    toLine() as pure\n      -> ok as Boolean\n      <- rtn as String: $ok\n\n    runPipeline()\n      stdout <- Stdout()\n      shared <- Tally()\n      worker <- (shared) is asyncWorkerFn as function\n        rtn: shared.total > 0\n      workers <- [ worker ]\n      cat workers | async | map with toLine > stdout","migrationContext":"Java/Go: nothing stops a worker/goroutine from mutating captured shared state; races are found (if ever) via stress testing or the race detector at runtime. EK9: mutation of thread-crossing shared state outside a MutexLock is a compile-time error.","keywords":["E08251","MutexLock","async","captured","concurrent","lock","mutation","race","shared","state","worker"],"primaryTopics":["shared state","data race","async"],"typicalErrors":[{"error":"E08251","correct":"rtn: shared.total > 0","incorrect":"shared.total: shared.total + 1\n        rtn: true","explanation":"The async worker captures the shared `Tally` and writes `shared.total` on a thread-pool thread with no lock, racing any other access. The safe form only reads in the worker; any mutation must go through a MutexLock body. See ek9 -h E08251."}],"companions":[]}
{"id":1351,"category":"Concurrency","question":"I got E08252 NESTED_ENTER_DIFFERENT_LOCK — how does nesting two locks form a deadlock cycle?","url":"https://ek9.io/qa/QA1351.html","alternatePhrasings":["Why does nesting two MutexLocks fail in EK9?","E08252 nested enter different lock fix","lock-order cycle from getA/getB nesting","two locks acquired in opposite order EK9"],"answer":"E08252 fires when EK9's deadlock detector finds two code paths that nest the same pair of distinct locks in OPPOSITE orders — one acquires lockA then lockB, another acquires lockB then lockA. Two threads running these paths concurrently can each hold one lock and wait forever for the other.\n\nWHAT THE ERROR MEANS\nEvery `lockA.enter(...)` with a `lockB.enter(...)` nested inside records an edge lockA -> lockB in a workspace-wide precedence graph. When updateA nests getB() inside getA() (edge A->B) and updateB nests getA() inside getB() (edge B->A), the graph has a cycle A->B->A — a deadlock.\n\nHOW TO FIX\nNever hold two locks at once when the acquisition order can contradict. Acquire them one at a time (release the first before taking the second), or unify the protected state under a single MutexLock of a record.\n\n  require pair.getA().enter(keyA)   //completes and releases\n  require pair.getB().enter(keyB)   //independent — no nesting\n\nWHY EK9 DETECTS THIS\nLock-order cycles only deadlock under specific interleavings, so they slip through testing. EK9 builds the precedence graph at compile time (the model the Linux kernel's lockdep uses at runtime) and refuses to compile any cycle.","ek9Example":"defines module qa.concurrency.nested.enter.different\n\n  defines class\n\n    LockPair\n      lockA as MutexLock of Integer: MutexLock(Integer(0))\n      lockB as MutexLock of Integer: MutexLock(Integer(0))\n      getA()\n        <- rtn as MutexLock of Integer: lockA\n      getB()\n        <- rtn as MutexLock of Integer: lockB\n      default operator ?\n\n  defines function\n\n    updateA()\n      -> pair as LockPair\n      keyA <- () is MutexKey of Integer as function\n        stdout <- Stdout()\n        stdout.println(lockedItem)\n      require pair.getA().enter(keyA)\n\n    updateB()\n      -> pair as LockPair\n      keyB <- () is MutexKey of Integer as function\n        stdout <- Stdout()\n        stdout.println(lockedItem)\n      require pair.getB().enter(keyB)","migrationContext":"Java/C#: lock-order deadlocks are found at runtime via thread dumps; static tools catch only simple cases. Go: the runtime detector fires only on total deadlock. EK9: workspace-wide static cycle detection on the lock precedence graph, rejected at compile time.","keywords":["E08252","MutexLock","concurrent","cycle","deadlock","enter","getA","getB","lock","nested","order","precedence"],"primaryTopics":["deadlock","lock-order","cycle detection"],"typicalErrors":[{"error":"E08252","correct":"updateA()\n      -> pair as LockPair\n      keyA <- () is MutexKey of Integer as function\n        stdout <- Stdout()\n        stdout.println(lockedItem)\n      require pair.getA().enter(keyA)\n\n    updateB()\n      -> pair as LockPair\n      keyB <- () is MutexKey of Integer as function\n        stdout <- Stdout()\n        stdout.println(lockedItem)\n      require pair.getB().enter(keyB)","incorrect":"updateA()\n      -> pair as LockPair\n      innerA <- () is MutexKey of Integer as function\n        stdout <- Stdout()\n        stdout.println(lockedItem)\n      outerA <- (pair, innerA) is MutexKey of Integer as function\n        require pair.getB().enter(innerA)\n      require pair.getA().enter(outerA)\n\n    updateB()\n      -> pair as LockPair\n      innerB <- () is MutexKey of Integer as function\n        stdout <- Stdout()\n        stdout.println(lockedItem)\n      outerB <- (pair, innerB) is MutexKey of Integer as function\n        require pair.getA().enter(innerB)\n      require pair.getB().enter(outerB)","explanation":"updateA nests getB() inside getA() (edge A->B) while updateB nests getA() inside getB() (edge B->A), forming a cycle A->B->A — a runtime deadlock. Fix by acquiring the locks sequentially (no nesting) or unifying them under one MutexLock of a record. See ek9 -h E08252."}],"companions":[]}
{"id":1352,"category":"Concurrency","question":"I got E08255 UNPROVABLE_LOCK_ORDER — why are two locks of the same type rejected even with no reverse nesting?","url":"https://ek9.io/qa/QA1352.html","alternatePhrasings":["Why does EK9 reject nesting two same-type locks?","E08255 unprovable lock order fix","bank transfer from.getLock()/to.getLock() deadlock EK9","two instances same lock type nested EK9"],"answer":"E08255 fires when two locks of the SAME type — held on caller-supplied objects — are nested, even if only ONE nesting direction appears in the code. Because the two objects (`from` and `to` here) are interchangeable at the call site, a single static `from` then `to` ordering can instantiate at runtime as both `A,B` and `B,A`. The compiler cannot PROVE a consistent global order, so it rejects the nesting.\n\nWHAT THE ERROR MEANS\nUnlike E08252 (two distinct lock fields, provably orderable), here both locks are `theLock` reached through same-type parameters. `moveFunds(x, y)` and `moveFunds(y, x)` both type-check, so nesting `to` inside `from` is simultaneously both acquisition orders — the classic bank-transfer / dining-philosophers deadlock.\n\nHOW TO FIX\nDon't nest same-type locks. Acquire them one at a time, or put the shared payload under a SINGLE MutexLock of an owning record so only one lock is ever taken.\n\n  require from.getLock().enter(fromKey)   //completes and releases\n  require to.getLock().enter(toKey)        //separate, unnested\n\nWHY EK9 DETECTS THIS\nSame-type nested locks are the textbook deadlock (two accounts, two philosophers). Runtime ordering tricks (lock by hashcode) are error-prone; EK9 rejects the shape at compile time.","ek9Example":"defines module qa.concurrency.unprovable.lock.order\n\n  defines class\n\n    Vault\n      theLock as MutexLock of Integer: MutexLock(Integer(0))\n      getLock() as pure\n        <- rtn as MutexLock of Integer: theLock\n      default operator ?\n\n  defines function\n\n    moveFunds()\n      ->\n        from as Vault\n        to as Vault\n      fromKey <- () is MutexKey of Integer as function\n        stdout <- Stdout()\n        stdout.println(lockedItem)\n      require from.getLock().enter(fromKey)\n\n      toKey <- () is MutexKey of Integer as function\n        stdout <- Stdout()\n        stdout.println(lockedItem)\n      require to.getLock().enter(toKey)","migrationContext":"Java: the canonical fix is to lock by System.identityHashCode ordering — easy to get wrong, invisible if wrong. EK9: nesting two same-type locks reached via interchangeable objects is a compile-time error (E08255); unify under one lock or acquire sequentially.","keywords":["E08255","MutexLock","concurrent","deadlock","instance","lock","nested","order","philosophers","same","transfer","type","unprovable"],"primaryTopics":["deadlock","unprovable order","same-type locks"],"typicalErrors":[{"error":"E08255","correct":"fromKey <- () is MutexKey of Integer as function\n        stdout <- Stdout()\n        stdout.println(lockedItem)\n      require from.getLock().enter(fromKey)\n\n      toKey <- () is MutexKey of Integer as function\n        stdout <- Stdout()\n        stdout.println(lockedItem)\n      require to.getLock().enter(toKey)","incorrect":"innerKey <- () is MutexKey of Integer as function\n        stdout <- Stdout()\n        stdout.println(lockedItem)\n      outerKey <- (to, innerKey) is MutexKey of Integer as function\n        require to.getLock().enter(innerKey)\n      require from.getLock().enter(outerKey)","explanation":"Both locks are `theLock` reached through same-type params `from`/`to`, which are interchangeable at the call site, so nesting `to` inside `from` instantiates as both orders at runtime — an unprovable order. Fix by acquiring sequentially or unifying under one MutexLock of a record. See ek9 -h E08255."}],"companions":[]}
{"id":1353,"category":"Constructor Delegation","question":"How do I extend a base whose no-argument constructor is private (E07176 / E07177)?","url":"https://ek9.io/qa/QA1353.html","alternatePhrasings":["Why does my sub-type get E07176 'constructor cannot call a private super constructor'?","E07177 the super type's no-argument constructor is private — what do I do?","My 'default Sub()' is rejected because the base has 'default private Base()'","A sub-class with no constructor won't compile when the super default is private"],"answer":"A base whose properties are uninitialised must make its no-arg default 'private' (E07175). That private default is NOT accessible to a sub-type, and EVERY constructor chains to a super constructor - explicitly (super(...) / this(...)) or via a hidden IMPLICIT super() to the super's no-arg. So a sub-type cannot reach the private base default.\n\nWHAT HAPPENS\n1. A sub-type that WRITES a no-arg default (or any constructor with no explicit super(...)) implicitly calls the private base default -> E07176.\n2. A sub-type with NO constructor at all: EK9 would normally synthesise a public no-arg default, but its hidden super() cannot reach the private base default, so none is synthesised - the type is unconstructable -> E07177.\n\nTHE FIX: call an accessible initialising super(...)\n  Shape as abstract\n    name as String?\n    default private Shape() as pure\n    Shape() as pure\n      -> n as String\n      name :=? String(n)\n\n  Circle is Shape\n    Circle() as pure\n      super(\"circle\")          // reaches the accessible, initialising Shape(String)\n\nEK9 does not mandate a no-argument constructor - the sub-type simply has none, and is constructed via the constructor that calls super(...).\n\nSee Q886 for E07175 (private default). See Q581 for super() delegation. See Q1346 for the 'T?' null-safety idiom.","ek9Example":"defines module qa.constructordelegation.privatesuperconstructor\n\n  defines class\n\n    Shape as abstract\n      name as String?\n\n      //Uninitialised property -> the no-arg default must be private (E07175). That private default is not\n      //reachable by any sub-type, so sub-types must chain to the accessible initialising Shape(String).\n      default private Shape() as pure\n\n      Shape() as pure\n        -> n as String\n        name :=? String(n)\n\n      name() as pure\n        <- rtn as String: String(name)\n\n      default operator ?\n\n    //Correct: Circle calls an accessible, initialising super(...). It has no synthesised no-arg default\n    //(EK9 does not mandate one) - it is constructed via this constructor.\n    Circle is Shape\n      Circle() as pure\n        super(\"circle\")\n\n  defines program\n\n    PrivateSuperDemo()\n      stdout <- Stdout()\n\n      c <- Circle()\n      stdout.println(c.name())","migrationContext":"Java/Kotlin: a subclass constructor implicitly calls super() and fails to compile if the base no-arg constructor is inaccessible ('there is no default constructor available'). EK9 reports the same class of problem precisely: E07176 when a sub-type constructor chains (implicitly or explicitly) to a private super, and E07177 when no constructor exists at all and no default can be synthesised.","keywords":["CONSTRUCTOR_REQUIRED_FOR_PRIVATE_SUPER","CONSTRUCTOR_USES_PRIVATE_SUPER","E07176","E07177","abstract","constructor","default","delegation","implicit","inheritance","private","sub-type","super","uninitialised"],"primaryTopics":["private super constructor","E07176","E07177","implicit super"],"typicalErrors":[{"error":"E07176","correct":"Circle() as pure\n        super(\"circle\")","incorrect":"default Circle() as pure","explanation":"A written 'default Circle()' (or any constructor without an explicit super(...)) implicitly calls the private Shape() - not accessible. Call an accessible initialising super(...) instead. See ek9 -h E07176."},{"error":"E07177","correct":"Circle() as pure\n        super(\"circle\")","incorrect":"Circle is Shape","explanation":"A sub-type with NO constructor cannot have a public default synthesised (its implicit super() could not reach the private Shape()). Define a constructor that calls an accessible super(...). See ek9 -h E07177."}],"companions":[]}
{"id":1354,"category":"Control Flow","question":"How do I use a guard in the expression form of a while loop?","url":"https://ek9.io/qa/QA1354.html","alternatePhrasings":["Can a while loop that returns a value also use a guard variable?","How do ?= and :=? guards work in a while expression?","while loop as an expression with a guard operator","How do I combine a guarded assignment with a while loop that yields a result?"],"answer":"The while loop has an expression form (it yields a value via a returning variable) and that form accepts the same guards as every other control-flow construct. The guard is written in the header, BEFORE the loop control, separated by 'then' (or 'with').\n\nWHILE EXPRESSION WITH A ?= GUARD\nThe guarded assignment (?=) assigns the right-hand side to the guard variable and checks whether it is SET. If unset, the whole loop body is skipped and the expression yields the returning variable's initial value:\n  reading as Integer?\n  looping <- true\n  total <- while reading ?= nextReading() then looping\n    <- rtn as Integer: 0\n    rtn: rtn + reading\n    looping: false\n  // total is 0 if nextReading() was unset, otherwise the accumulated value\n\nWHILE EXPRESSION WITH A :=? GUARD\nAssign-if-unset (:=?) only assigns when the guard variable is currently unset; it still gates the body on the resulting set-state:\n  cached as Integer?\n  loopOnce <- true\n  lazy <- while cached :=? nextReading() then loopOnce\n    <- rtn as Integer: -1\n    rtn: cached\n    loopOnce: false\n\nHEADER ORDER MATTERS\nThe guard comes FIRST, then the control condition after 'then' (or 'with'):\n  while <guard> then <control>\nNot 'while <control> then <guard>'. The guard is evaluated once at entry; if it leaves the guard variable unset the body never runs.\n\nRETURNING VARIABLE\nThe returning variable ('<- rtn as T: initial') is declared as the first line of the body and MUST have an initial value, so a skipped body still yields a defined result. This is why a guard-unset while expression returns the initialiser.\n\nSee Q77 for while guards in statement form. See Q81 for the while expression form without a guard. See Q1361 for the same guard used uniformly across all six expression-form constructs. See Q74 for the three guard operators.","ek9Example":"defines module qa.controlflow.guardwhileexpression\n\n  defines function\n\n    nextReading()\n      <- rtn as Integer: 42\n\n  defines program\n\n    GuardWhileExpressionDemo()\n      stdout <- Stdout()\n\n      // while EXPRESSION with a ?= guarded-assignment guard\n      reading as Integer?\n      looping <- true\n      total <- while reading ?= nextReading() then looping\n        <- rtn as Integer: 0\n        rtn: rtn + reading\n        looping: false\n      stdout.println(`accumulated total is ${total}`)\n\n      // while EXPRESSION with a :=? assign-if-unset guard\n      cached as Integer?\n      loopOnce <- true\n      lazy <- while cached :=? nextReading() then loopOnce\n        <- rtn as Integer: -1\n        rtn: cached\n        loopOnce: false\n      stdout.println(`lazy value is ${lazy}`)","migrationContext":"Java: no equivalent. A while loop is a statement, never an expression, and cannot declare a loop-scoped guarded variable. You would write a nullable variable, a manual isSet check, and a mutable accumulator across three or more statements. Kotlin: while is a statement; you would use a sequence/fold to get a value. Rust: 'while let Some(x) = iter.next()' binds and loops but is still a statement, not a value-producing expression. EK9: the while expression yields a value AND accepts a guard (<-, ?=, :=?) in one header, with the returning variable guaranteeing a defined result even when the guard is unset.","keywords":[":=?","?=","assignment","control","expression","flow","guard","guarded","isset","loop","returning","then","value","while"],"primaryTopics":["while expression","guard variable","guarded assignment"],"typicalErrors":[{"error":"E02001","correct":"total <- while reading ?= nextReading() then looping\n        <- rtn as Integer: 0\n        rtn: rtn + reading\n        looping: false","incorrect":"total <- while looping then reading ?= nextReading()\n        <- rtn as Integer: 0\n        rtn: rtn + reading","explanation":"The guard is the pre-flow part and must come FIRST in the header; the loop control comes after 'then' (or 'with'). Writing the control first and the guard second is not valid. See ek9 -h E02001."}],"companions":[]}
{"id":1355,"category":"Control Flow","question":"How do guards work in a do/while loop, in both statement and expression form?","url":"https://ek9.io/qa/QA1355.html","alternatePhrasings":["Can a do-while loop use a guard variable?","How do I use ?= or :=? in a do/while loop?","do-while as an expression with a guard","guarded assignment in a do while loop"],"answer":"The do/while loop accepts a guard in its header (after 'do'), and — like the other loops — it has an expression form that yields a value. The guard is checked once at entry; if it leaves the guard variable unset the body is skipped, even though a do/while normally runs its body at least once.\n\nDO/WHILE STATEMENT WITH A ?= GUARD\n  first as String?\n  more <- true\n  do first ?= fetchRecord()\n    stdout.println(`got ${first}`)\n    more: false\n  while more\nThe guarded assignment (?=) assigns fetchRecord() to 'first' and checks it is SET before the body runs.\n\nDO/WHILE EXPRESSION WITH A :=? GUARD\nThe expression form declares a returning variable as the first body line and yields its final value:\n  cached as String?\n  loopOnce <- true\n  summary <- do cached :=? fetchRecord()\n    <- rtn as String: \"none\"\n    rtn: cached\n    loopOnce: false\n  while loopOnce\nAssign-if-unset (:=?) only assigns when 'cached' is currently unset. If the guard leaves it unset, the body is skipped and 'summary' is the initial value \"none\".\n\nGUARD POSITION\nThe guard is the pre-flow, written directly after 'do'; the loop control stays where it belongs, after the body in the trailing 'while' clause.\n\nSee Q67 for the plain do/while. See Q77 for while guards. See Q1354 for the while expression form with a guard. See Q1361 for guards used uniformly across all expression-form constructs.","ek9Example":"defines module qa.controlflow.guarddowhile\n\n  defines function\n\n    fetchRecord()\n      <- rtn as String: \"record-A\"\n\n  defines program\n\n    GuardDoWhileDemo()\n      stdout <- Stdout()\n\n      // do/while STATEMENT with a ?= guard (guard checked once, after 'do')\n      first as String?\n      more <- true\n      do first ?= fetchRecord()\n        stdout.println(`got ${first}`)\n        more: false\n      while more\n\n      // do/while EXPRESSION with a :=? assign-if-unset guard\n      cached as String?\n      loopOnce <- true\n      summary <- do cached :=? fetchRecord()\n        <- rtn as String: \"none\"\n        rtn: cached\n        loopOnce: false\n      while loopOnce\n      stdout.println(`summary is ${summary}`)","migrationContext":"Java: do-while is a statement only; it always runs the body once and cannot gate the first iteration on a guarded, loop-scoped variable, nor produce a value. You would use a nullable variable plus a manual check. Kotlin/Swift/Rust: do-while (or repeat-while) is likewise a statement with no guard binding and no value form. EK9: do/while accepts <-, ?= and :=? guards that can skip even the first iteration when unset, and has an expression form that yields the returning variable.","keywords":[":=?","?=","control","do","do-while","expression","flow","guard","guarded","isset","loop","returning","while"],"primaryTopics":["do-while loop","guard variable","guarded assignment"],"typicalErrors":[{"error":"E02001","correct":"do first ?= fetchRecord()\n        stdout.println(`got ${first}`)\n        more: false\n      while more","incorrect":"do\n        first ?= fetchRecord()\n        stdout.println(`got ${first}`)\n      while more","explanation":"The guard belongs in the header, directly after 'do' (do first ?= fetchRecord()), not as the first statement of the body. As a guard it can skip the whole body when unset. See ek9 -h E02001."}],"companions":[]}
{"id":1356,"category":"Control Flow","question":"How do I use a guard in a for-range loop, including the expression form?","url":"https://ek9.io/qa/QA1356.html","alternatePhrasings":["Can a for i in 1 ... n loop use a guard variable?","for-range as an expression with a ?= guard","guarded assignment in a numeric for loop","How do I put a guard before the range in a for loop?"],"answer":"A for-range loop ('for i in start ... end') accepts a guard in its header, written BEFORE the loop variable and separated by 'with' (or 'then'). The for loop also has an expression form that yields a value via a returning variable.\n\nFOR-RANGE EXPRESSION WITH A ?= GUARD\nThe guard comes first, then 'with', then the loop variable and range:\n  step as Integer?\n  total <- for step ?= getStep() with i in 1 ... 5\n    <- rtn as Integer: 0\n    rtn: rtn + (i * step)\nIf getStep() is unset the guarded assignment leaves 'step' unset, the loop body is skipped, and 'total' is the initial value 0.\n\nFOR-RANGE STATEMENT WITH A :=? GUARD\n  factor as Integer?\n  sum <- 0\n  for factor :=? getStep() with i in 1 ... 3\n    sum: sum + (i * factor)\n\nHEADER ORDER\nThe grammar is 'for <guard> with <var> in <range>'. The guard variable ('step', 'factor') is SEPARATE from the loop variable ('i'). Putting the guard after the range ('for i in 1 ... 5 with step ?= ...') is NOT valid.\n\nBY STEP\nA for-range may add 'by <step>' for non-unit or descending ranges; the guard still comes first: 'for g <- getG() with i in 10 ... 1 by -1'.\n\nSee Q64 for the plain for-range. See Q1149 for 'by'. See Q1357 for for-in guards. See Q1361 for guards across all expression-form constructs.","ek9Example":"defines module qa.controlflow.guardforrangeexpression\n\n  defines function\n\n    getStep()\n      <- rtn as Integer: 2\n\n  defines program\n\n    GuardForRangeExpressionDemo()\n      stdout <- Stdout()\n\n      // for-range EXPRESSION with a ?= guard (guard FIRST, then 'with i in range')\n      step as Integer?\n      total <- for step ?= getStep() with i in 1 ... 5\n        <- rtn as Integer: 0\n        rtn: rtn + (i * step)\n      stdout.println(`weighted total is ${total}`)\n\n      // for-range STATEMENT with a :=? guard\n      factor as Integer?\n      sum <- 0\n      for factor :=? getStep() with i in 1 ... 3\n        sum: sum + (i * factor)\n      stdout.println(`sum is ${sum}`)","migrationContext":"Java: a for loop is a statement and cannot bind a guarded, isSet-checked variable in its header nor yield a value. You would compute a nullable outside the loop and check it manually. Python/Kotlin: for-comprehensions/folds produce values but have no isSet-guard concept. EK9: the for-range header takes a guard (<-, ?=, :=?) before the loop variable, and its expression form yields the returning variable — with the guard able to skip the whole loop when unset.","keywords":[":=?","?=","by","expression","for","for-range","guard","guarded","isset","loop","range","returning","with"],"primaryTopics":["for range loop","guard variable","for expression"],"typicalErrors":[{"error":"E02001","correct":"total <- for step ?= getStep() with i in 1 ... 5\n        <- rtn as Integer: 0\n        rtn: rtn + (i * step)","incorrect":"total <- for i in 1 ... 5 with step ?= getStep()\n        <- rtn as Integer: 0\n        rtn: rtn + (i * step)","explanation":"The guard is the pre-flow and must come BEFORE the loop variable: 'for <guard> with <var> in <range>'. The guard variable is separate from the loop variable. See ek9 -h E02001."}],"companions":[]}
{"id":1357,"category":"Control Flow","question":"How do I use a guard in a for-in loop, including the expression form?","url":"https://ek9.io/qa/QA1357.html","alternatePhrasings":["Can a for item in collection loop use a guard variable?","for-in as an expression with a ?= guard","guarded assignment when iterating a collection","How do I guard a for-each loop that returns a value?"],"answer":"A for-in loop ('for item in collection') accepts a guard in its header, written BEFORE the loop variable and separated by 'with' (or 'then'). The for-in loop also has an expression form yielding a value via a returning variable.\n\nFOR-IN EXPRESSION WITH A ?= GUARD\n  prefix as String?\n  joined <- for prefix ?= getPrefix() with item in items\n    <- rtn as String: \"\"\n    rtn: `${rtn}${prefix}${item}`\nThe guarded assignment (?=) assigns getPrefix() to 'prefix' and checks it is SET. If unset, the loop body is skipped and 'joined' is the initial \"\". (Note: build strings with interpolation `${...}`, not 3+ chained '+', which EK9 rejects with E11068.)\n\nFOR-IN STATEMENT WITH A :=? GUARD\n  marker as String?\n  count <- 0\n  for marker :=? getPrefix() with item in items\n    if marker?\n      count: count + 1\n\nGUARD vs LOOP VARIABLE\nThe guard variable ('prefix', 'marker') is separate from the loop variable ('item'). Order is 'for <guard> with <var> in <collection>'; the guard cannot go after the collection.\n\nSee Q65 for the plain for-in. See Q1356 for for-range guards. See Q1361 for guards across all expression-form constructs. See Q76 for guards in for loops overview.","ek9Example":"defines module qa.controlflow.guardforinexpression\n\n  defines program\n\n    GuardForInExpressionDemo()\n      stdout <- Stdout()\n      items <- [\"alpha\", \"beta\", \"gamma\"]\n\n      // for-in EXPRESSION with a ?= guard (guard FIRST, then 'with item in collection')\n      prefix as String?\n      joined <- for prefix ?= getPrefix() with item in items\n        <- rtn as String: \"\"\n        rtn: `${rtn}${prefix}${item}`\n      stdout.println(`joined: ${joined}`)\n\n      // for-in STATEMENT with a :=? guard\n      marker as String?\n      count <- 0\n      for marker :=? getPrefix() with item in items\n        if marker?\n          count: count + 1\n      stdout.println(`count is ${count}`)\n\n  defines function\n\n    getPrefix()\n      <- rtn as String: \"> \"","migrationContext":"Java: enhanced-for is a statement; it cannot bind a guarded, isSet-checked header variable nor produce a value. Kotlin/Swift: for-each is a statement; value production needs map/fold. EK9: for-in takes a guard (<-, ?=, :=?) before the loop variable, and its expression form yields the returning variable, with the guard able to skip iteration entirely when unset.","keywords":[":=?","?=","collection","expression","for","for-each","for-in","guard","guarded","isset","iterate","returning","with"],"primaryTopics":["for in loop","guard variable","for expression"],"typicalErrors":[{"error":"E02001","correct":"joined <- for prefix ?= getPrefix() with item in items\n        <- rtn as String: \"\"\n        rtn: `${rtn}${prefix}${item}`","incorrect":"joined <- for item in items with prefix ?= getPrefix()\n        <- rtn as String: \"\"\n        rtn: `${rtn}${prefix}${item}`","explanation":"The guard is the pre-flow and must come BEFORE the loop variable: 'for <guard> with <var> in <collection>'. See ek9 -h E02001."}],"companions":[]}
{"id":1358,"category":"Control Flow","question":"How do I use a ?= or :=? guard in a try, in both statement and expression form?","url":"https://ek9.io/qa/QA1358.html","alternatePhrasings":["Can a try/catch use a guarded assignment in its header?","try as an expression with a guard variable","guarded assignment with try catch","How do I combine a guard with a try that returns a value?"],"answer":"A try accepts a guard in its header (directly after 'try'), and has an expression form that yields a value via a returning variable. If the guard leaves its variable unset, the try body is skipped and the expression yields the returning variable's initial value.\n\nTRY EXPRESSION WITH A ?= GUARD\n  config as String?\n  chosen <- try config ?= loadConfig()\n    <- rtn as String: \"default.yaml\"\n    rtn: config\n  catch\n    -> ex as Exception\n    rtn: `error ${ex}`\nThe guarded assignment (?=) assigns loadConfig() to 'config' and checks it is SET. If unset, the body is skipped and 'chosen' is \"default.yaml\".\n\nTRY STATEMENT WITH A :=? GUARD\n  cached as String?\n  try cached :=? loadConfig()\n    stdout.println(`loaded ${cached}`)\n  catch\n    -> ex as Exception\n    stdout.println(`failed: ${ex}`)\nAssign-if-unset (:=?) only assigns when 'cached' is currently unset, then gates the body on the result.\n\nGUARD POSITION\nThe guard is the pre-flow, written right after 'try' — 'try <guard>' — before the body, resource header ('-> r <- ...') and returning variable. catch and finally are unaffected.\n\nSee Q78 for try guards. See Q1157 for try guard expressions. See Q137 for try-with-resources. See Q1361 for guards across all expression-form constructs.","ek9Example":"defines module qa.controlflow.guardtryexpression\n\n  defines function\n\n    loadConfig()\n      <- rtn as String: \"prod.yaml\"\n\n  defines program\n\n    GuardTryExpressionDemo()\n      stdout <- Stdout()\n\n      // try EXPRESSION with a ?= guard (guard right after 'try')\n      config as String?\n      chosen <- try config ?= loadConfig()\n        <- rtn as String: \"default.yaml\"\n        rtn: config\n      catch\n        -> ex as Exception\n        rtn: `error ${ex}`\n      stdout.println(`chosen config: ${chosen}`)\n\n      // try STATEMENT with a :=? assign-if-unset guard\n      cached as String?\n      try cached :=? loadConfig()\n        stdout.println(`loaded ${cached}`)\n      catch\n        -> problem as Exception\n        stdout.println(`failed: ${problem}`)","migrationContext":"Java: try is a statement, cannot bind a guarded loop-scoped variable in its header, and cannot yield a value; finally cannot set a result. You would null-check before the try. EK9: try takes a guard (<-, ?=, :=?) right after 'try', has an expression form yielding the returning variable, and the guard can skip the body entirely when unset.","keywords":[":=?","?=","catch","control","exception","expression","flow","guard","guarded","isset","returning","try"],"primaryTopics":["try expression","guard variable","guarded assignment"],"typicalErrors":[{"error":"E02001","correct":"chosen <- try config ?= loadConfig()\n        <- rtn as String: \"default.yaml\"\n        rtn: config\n      catch\n        -> ex as Exception\n        rtn: `error ${ex}`","incorrect":"chosen <- try\n        config ?= loadConfig()\n        <- rtn as String: \"default.yaml\"\n        rtn: config","explanation":"The guard belongs in the header directly after 'try' (try config ?= loadConfig()), not as the first statement of the body. As a guard it gates the whole body on the guard variable being set. See ek9 -h E02001."}],"companions":[]}
{"id":1359,"category":"Control Flow","question":"How do I use a :=? assign-if-unset guard in a switch expression?","url":"https://ek9.io/qa/QA1359.html","alternatePhrasings":["switch expression with a guarded assignment","Can a switch that returns a value lazily initialise its control variable?","assign-if-unset guard in a given/switch","How do I default the switch control with :=? before matching?"],"answer":"The switch (or 'given') expression accepts a guard in its header. The guard runs once, before matching; if it leaves the guard variable unset the whole switch body is skipped and the expression yields the returning variable's initial value.\n\nSWITCH EXPRESSION WITH A :=? GUARD\nAssign-if-unset (:=?) sets the control variable only when it is currently unset, then the switch matches on it:\n  tier as String?\n  discount <- switch tier :=? lookupTier() with tier\n    <- rtn as Integer: 0\n    case \"gold\"\n      rtn: 20\n    case \"silver\"\n      rtn: 10\n    default\n      rtn: 5\nIf lookupTier() is unset, 'tier' stays unset, the body is skipped, and 'discount' is the initial 0.\n\nSWITCH STATEMENT WITH A :=? GUARD\nThe same guard works without a returning variable:\n  level as String?\n  switch level :=? lookupTier() with level\n    case \"gold\"\n      stdout.println(\"premium\")\n    default\n      stdout.println(\"standard\")\n\nHEADER SHAPE\n'switch <guard> with <control>' — the guard is the pre-flow; the value being matched comes after 'with'. Contrast ?= (guarded assignment, always assigns then checks) with :=? (assigns only when unset).\n\nSee Q75 for switch guards. See Q837 for the ?= guard in a switch expression. See Q68 for the switch expression. See Q1361 for guards across all expression-form constructs.","ek9Example":"defines module qa.controlflow.guardswitchexpression\n\n  defines function\n\n    lookupTier()\n      <- rtn as String: \"gold\"\n\n  defines program\n\n    GuardSwitchExpressionDemo()\n      stdout <- Stdout()\n\n      // switch EXPRESSION with a :=? assign-if-unset guard\n      tier as String?\n      discount <- switch tier :=? lookupTier() with tier\n        <- rtn as Integer: 0\n        case \"gold\"\n          rtn: 20\n        case \"silver\"\n          rtn: 10\n        default\n          rtn: 5\n      stdout.println(`discount is ${discount}`)\n\n      // switch STATEMENT with a :=? guard\n      level as String?\n      switch level :=? lookupTier() with level\n        case \"silver\"\n          stdout.println(\"standard tier\")\n        default\n          stdout.println(\"premium tier\")","migrationContext":"Java: switch is a statement (or, since 14, an expression) but has no header guard and no isSet/assign-if-unset concept; you would default the variable in a separate statement. EK9: the switch/given expression takes a guard (<-, ?=, :=?) that can lazily initialise the control and skip the whole switch when unset, while still yielding the returning variable.","keywords":[":=?","assign","case","control","default","expression","given","guard","isset","returning","switch","unset"],"primaryTopics":["switch expression","guarded assignment","guard variable"],"typicalErrors":[{"error":"E02001","correct":"discount <- switch tier :=? lookupTier() with tier\n        <- rtn as Integer: 0\n        case \"gold\"\n          rtn: 20\n        default\n          rtn: 5","incorrect":"discount <- switch tier with tier :=? lookupTier()\n        <- rtn as Integer: 0\n        case \"gold\"\n          rtn: 20\n        default\n          rtn: 5","explanation":"The guard is the pre-flow and comes first; the matched control follows 'with'. Order is 'switch <guard> with <control>'. See ek9 -h E02001."}],"companions":[]}
{"id":1360,"category":"Control Flow","question":"What is the ternary operator in EK9 and how do its ':' and 'else' forms differ?","url":"https://ek9.io/qa/QA1360.html","alternatePhrasings":["How do I write an if-expression in EK9?","conditional value selection without a switch","the ternary <- : operator","difference between ternary ':' and 'else'"],"answer":"EK9 has no if-EXPRESSION: 'if' is always a statement. When you want a value chosen by a condition, use the TERNARY operator (or a switch expression). The ternary is: 'condition <- valueIfTrue <separator> valueIfFalse', where the separator is either ':' or the keyword 'else' — the two forms are IDENTICAL in meaning.\n\nTERNARY WITH ':'\n  passMark <- 90\n  grade <- score >= passMark <- \"A\" : \"B\"\nReads: if 'score >= passMark' then \"A\" else \"B\". (Comparison literals must be named — a bare 'score >= 90' is rejected with E11064 — so bind the threshold first.)\n\nTERNARY WITH 'else'\n  grade <- score >= passMark <- \"A\" else \"B\"\nIdentical result; choose whichever reads better. 'else' is often clearer in longer expressions; ':' is more compact.\n\nONLY THE SELECTED ARM IS PRODUCED\nThe condition, and each arm, are ordinary expressions; only the chosen arm's value becomes the result.\n\nTERNARY IS NOT COALESCING\nThe ternary chooses on a Boolean condition. It is distinct from the coalescing operators, which choose on set-state: '?:' (isSet coalescing, 'a ?: b' yields a if a is set else b). Use a ternary for a genuine Boolean test; use '?:' when you mean 'a, or b if a is unset'.\n\nWHY NOT if-as-expression\n'if' stays a statement so that all value production is explicit (ternary or switch), keeping branch side-effects and value-selection visually separate.\n\nSee Q61 for the if statement. See Q68 for the switch expression (the other value-producing conditional). See Q243 for coalescing operators. See Q898 for isSet vs ternary.","ek9Example":"defines module qa.controlflow.ternarycolonelse\n\n  defines program\n\n    TernaryColonElseDemo()\n      stdout <- Stdout()\n      score <- 85\n      passMark <- 90\n\n      // ternary with ':' — condition <- valueIfTrue : valueIfFalse\n      grade <- score >= passMark <- \"A\" : \"B\"\n      stdout.println(`grade (colon form): ${grade}`)\n\n      // ternary with 'else' — identical meaning, different separator keyword\n      band <- score >= passMark <- \"high\" else \"normal\"\n      stdout.println(`band (else form): ${band}`)","migrationContext":"Java: 'cond ? a : b' — EK9's ':' form is the direct analogue but the condition is separated by '<-' ('cond <- a : b'). Java has no 'else' keyword variant. Python: 'a if cond else b' mirrors EK9's 'else' form. Kotlin/Rust/Swift: 'if' is itself an expression; EK9 deliberately keeps 'if' a statement and offers the ternary (and switch expression) for value selection so branch effects and value selection stay separate.","keywords":["coalescing","colon","conditional","control","else","expression","flow","if-expression","select","ternary","value"],"primaryTopics":["ternary operator","conditional expression","if expression"],"typicalErrors":[{"error":"E02001","correct":"passMark <- 90\n      grade <- score >= passMark <- \"A\" : \"B\"","incorrect":"grade <- if score >= 90 then \"A\" else \"B\"","explanation":"EK9 has no if-expression. Use the ternary 'condition <- valueIfTrue : valueIfFalse' (or 'else' instead of ':'), or a switch expression. 'if' is a statement only. See ek9 -h E02001."}],"companions":[]}
{"id":1361,"category":"Control Flow","question":"Do guard variables work the same way in every expression-form control-flow construct?","url":"https://ek9.io/qa/QA1361.html","alternatePhrasings":["Is the guard syntax uniform across while, for, switch and try expressions?","Can I use the same ?= guard in a while, for, switch and try that all return values?","guards across all control flow expression forms","one guard pattern for every value-producing construct"],"answer":"Yes. EK9 offers a UNIFORM guard mechanism across all six expression-form control-flow constructs: while, do/while, for-range, for-in, switch/given, and try. In every one, a guard in the header (<- declaration, ?= guarded assignment, or :=? assign-if-unset) is evaluated once at entry; if it leaves the guard variable unset the whole body is skipped and the expression yields the returning variable's initial value. The same three operators, the same skip-on-unset semantics, the same 'returning variable must be initialised' rule — everywhere.\n\nTHE SAME ?= GUARD, SIX CONSTRUCTS (each yields source() when set):\n  while:     wResult <- while g ?= source() then loop ...\n  do/while:  dResult <- do g ?= source() ... while loop\n  for-range: rResult <- for g ?= source() with i in 1 ... 1 ...\n  for-in:    fResult <- for g ?= source() with item in [2] ...\n  switch:    sResult <- switch g ?= source() with g ...\n  try:       tResult <- try g ?= source() ...\n\nHEADER POSITION\nThe guard is always the pre-flow, written FIRST: after 'while'/'do'/'for'/'switch'/'try', before the construct's own control ('then'/'with' condition, range, collection, or trailing 'while'). It is never placed after the control.\n\nIF IS THE EXCEPTION\n'if' has no expression form — its value-producing counterpart is the ternary ('cond <- a : b'). Every OTHER construct has both statement and expression forms, and the guard behaves identically in both.\n\nWHY UNIFORM\nOne rule to learn, no per-construct special cases, and the returning-variable-initialised requirement means a guard-skipped body can never yield an undefined value. This is enforced by the compiler (E08050).\n\nSee Q1354 (while), Q1355 (do-while), Q1356 (for-range), Q1357 (for-in), Q1359 (switch), Q1358 (try) for each construct in detail. See Q74 for the three guard operators. See Q1360 for the ternary (the if-expression equivalent).","ek9Example":"defines module qa.controlflow.guardsuniform\n\n  defines function\n\n    source()\n      <- rtn as Integer: 7\n\n  defines program\n\n    GuardsUniformDemo()\n      stdout <- Stdout()\n\n      // The SAME ?= guard, in each of the six expression-form constructs.\n      // source() is set, so every body runs once and the result is 7.\n\n      // The SAME ?= guard, with the SAME skip-on-unset semantics and the SAME\n      // 'returning variable must be initialised' rule, shown in three representative\n      // expression forms. source() is set, so each body runs once and yields 7.\n      // (do-while, for-in and switch behave identically — see Q1355/Q1357/Q1359.)\n\n      // while expression: guard, then 'then <control>'\n      loopFlag <- true\n      whileGuard as Integer?\n      whileValue <- while whileGuard ?= source() then loopFlag\n        <- rtn as Integer: -1\n        rtn: whileGuard\n        loopFlag: false\n\n      // for-range expression: guard, then 'with <var> in <range>'\n      rangeGuard as Integer?\n      rangeValue <- for rangeGuard ?= source() with i in 1 ... 1\n        <- rtn as Integer: -2\n        require i?\n        rtn: rangeGuard\n\n      // try expression: guard right after 'try'\n      tryGuard as Integer?\n      tryValue <- try tryGuard ?= source()\n        <- rtn as Integer: -3\n        rtn: tryGuard\n\n      stdout.println(`while=${whileValue} forRange=${rangeValue} try=${tryValue}`)","migrationContext":"No mainstream language offers a single guard mechanism that is uniform across all loop, switch and try constructs AND lets each yield a value. Rust has 'while let'/'if let' but not for switch/try and they are statements; Swift has 'if let'/'guard let' but not across loops as value-producers. EK9 unifies declaration/guarded-assignment/assign-if-unset guards over while, do-while, for-range, for-in, switch and try, each with a value-producing expression form and a compiler-guaranteed initialised result.","keywords":[":=?","?=","consistent","control","do-while","expression","flow","for","guard","returning","switch","try","uniform","while"],"primaryTopics":["guard variable","expression form","control flow uniformity"],"typicalErrors":[{"error":"E08050","correct":"wResult <- while g ?= source() then loop\n        <- rtn as Integer: -1\n        rtn: g\n        loop: false","incorrect":"wResult <- while g ?= source() then loop\n        <- rtn as Integer?\n        rtn: g\n        loop: false","explanation":"In every expression form the returning variable must be INITIALISED so a guard-skipped body still yields a defined value. Declaring it uninitialised ('<- rtn as Integer?') is rejected by E08050. See ek9 -h E08050."}],"companions":[]}
{"id":1362,"category":"Code Quality","question":"What is the input-variation (input-variety) metric in EK9?","url":"https://ek9.io/qa/QA1362.html","alternatePhrasings":["What do the input-variety floor and ceiling mean?","How does EK9 measure test input variety?","What is the type-variety rollup shown in the complexity hover?","Why does my method show an input-variety floor and ceiling?"],"answer":"The input-variation metric scores whether tests drive a reasonable VARIETY of input value-classes through each public construct. It is a floor detector, not a correctness certificate.\n\nCODE COVERAGE IS NOT CORRECTNESS\nEK9 also has code coverage built into the compiler and the -t test runner (the E83001 publish gate). But 100% code coverage only means every line and branch executed at least once during the tests - it does NOT prove the function is functionally correct, and it does NOT mean the inputs were varied. You can reach 100% coverage on multiply(Integer, Integer) with a single multiply(2, 2) call: every line runs, coverage is green, yet negatives, zero, overflow (min/max) and unset were never tried. Coverage answers 'did the code run?'; input-variety answers 'across how many meaningfully-different input classes did it run?'. The two are complementary, and neither certifies correctness - even full coverage AND full variety cannot prove that an assertion's expected value is actually right (that is what mutation testing, EK9's eventual stronger gate, targets). Input-variety exists to catch the common blind spot: green coverage masking a thin, low-variety test suite - the classic multiply(2, 2) case.\n\nVALUE-CLASSES\nEach parameter type admits a set of value-classes. Integer has 9 (unset, zero, negative, positive, typical, min, max, near-min, near-max), Boolean 3 (true/false/unset), String 7 (heuristic), a user enumeration = its members + unset. These are derived structurally from the type, no literals.\n\nTWO FIGURES PER CALLABLE\nFLOOR (the headline) = the 1-wise 'each-choice' count = the sum of each input's class-count. It asks: was each value-class of each input exercised at least once? For combine(Integer, Integer) the floor is 9 + 9 = 18 (reachable by about 9 well-chosen tests). This is the number the optional publish gate is built around.\nCEILING (informational) = the pairwise opportunity = the sum over input pairs of classCount x classCount. For combine it is 9 x 9 = 81. It is an upper bound, never a test target - good 1-wise testing only reaches around 11% of it.\n\nRECORDS DECOMPOSE TO FIELDS\nA record is transparent, so it contributes its fields as separate leaf dimensions, never an atomic product. A Point{x, y} used by an operator is scored on its x and y leaves - so packaging fields into a record does not change the number, and Point.<=>(Point) is floor 36 / ceiling 486, not 81 x 81.\n\nRECEIVER COUNTS\nAn instance method is scored against its RECEIVER's state too (the receiver is an implicit input), so a no-arg pop() or isSet() is measured against the states its object can be in - not treated as having no inputs.\n\nPER-TYPE ROLLUP\nA class/record/component/trait also shows a rollup summing its whole surface (constructors + methods), so you see how well the type as a whole is variety-tested, not just one member.\n\nWHERE TO SEE IT\nThe MCP tool ek9_query_complexity in inputVariety mode lists every callable's floor and ceiling plus the per-type rollup; the IDE complexity hover shows the same figures per construct. A low floor means under-tested (or the type over-partitions its inputs); it never proves correctness. See Q811 for built-in code coverage, Q312 for the complexity metrics, and Q311 for the quality checks catalog.","ek9Example":"defines module qa.codequality.inputvariety\n\n  defines function\n\n    <?-\n      Two Integer parameters admit 9 value-classes each, so the input-variation\n      floor is 9 + 9 = 18 (exercise each class of each input once) and the pairwise\n      ceiling is 9 x 9 = 81 (the informational upper bound).\n    -?>\n    combine() as pure\n      ->\n        left as Integer\n        right as Integer\n      <- rtn as Integer: left + right\n\n  defines record\n\n    <?-\n      A record decomposes into its field leaves (x, y), so it is scored on its\n      fields rather than as an atomic product - packaging does not change the number.\n    -?>\n    Point\n      x Integer?\n      y Integer?\n\n      default private Point()\n\n      Point()\n        ->\n          initX as Integer\n          initY as Integer\n        this.x: Integer(initX)\n        this.y: Integer(initY)\n\n      operator $ as pure\n        <- rtn as String: `${x},${y}`\n\n      default operator ?\n\n  defines program\n\n    InputVarietyDemo()\n      stdout <- Stdout()\n\n      total <- combine(2, 3)\n      stdout.println(`${total}`)\n\n      p <- Point(1, 2)\n      stdout.println(`${p}`)","migrationContext":"Combinatorial/pairwise testing (PICT, ACTS) and mutation testing (PIT for Java, Stryker for JS) all measure input adequacy but as separate, optional external tools run in CI. SonarQube reports coverage but not input-class variety. EK9 builds a cheap, always-on input-variety floor into the compiler alongside the other quality metrics, with mutation testing as the eventual stronger successor.","keywords":["boundary","ceiling","correctness","coverage","each-choice","equivalence","floor","input-variation","input-variety","metric","mutation","pairwise","quality","receiver","testing","value-class","variety"],"primaryTopics":["input variety","input-variation metric","test variety"],"typicalErrors":[],"companions":[]}
{"id":1363,"category":"Operators and Expressions","question":"Can I apply a mutating operator like += to a method-call result?","url":"https://ek9.io/qa/QA1363.html","alternatePhrasings":["What does repository.items() += entry do?","Can I mutate the collection returned by a method?","Is assigning to a method-call result allowed in EK9?","Why does myObject.getList() += x compile but myObject.getList() := x not?"],"answer":"Yes - a MUTATING operator can be applied to a method-call result, but a plain or guarded ASSIGNMENT cannot.\n\nMUTATING OPERATORS ON A CALL RESULT (ALLOWED)\nEK9 methods return object references. A mutating operator (+=, -=, *=, /=, :~:, :^:, :=:) applied to a call result mutates the object the call returns IN PLACE:\n  repository.items() += entry\nThis is exactly equivalent to repository.items()._addAss(entry) - the operator spelling of calling a mutating method on the returned object. It is NOT 'reassign the result of items()'; EK9's += never stores back, it always mutates the receiver.\n\nWHEN THE MUTATION PERSISTS (AND WHEN IT DOES NOT)\nWhether the mutation is observable depends entirely on WHAT the method returns:\n  - Returns the internal field BY REFERENCE  -> the mutation persists. This is a deliberate 'here is my collection, modify it' exposure.\n  - Returns a fresh COPY                     -> the mutation is applied to the throwaway copy and is lost.\nThe caller cannot tell which from the call site, so this is a property of the accessor's contract. It is the same aliasing consideration as any mutating method call on a result (e.g. getListCopy().add(x)).\n\nSIMPLE / GUARDED ASSIGNMENT ON A CALL RESULT (REJECTED - E07895)\nA simple (:=, :) or guarded (:=?) assignment needs a storage location to store into. A method-call result is a temporary value, not a location, so there is nothing to assign to:\n  repository.items() := List()    // E07895: not an assignable target\nNote this differs from assigning to a FIELD reached through a call, which targets a real field slot and IS allowed:\n  getRecord().name := \"Steve\"      // OK - .name is an assignable field\n\nSee Q241 for mutation vs pure operators. See ek9 -h E07895 for the assignment restriction.","ek9Example":"defines module qa.operators.mutate.call.result\n\n  defines class\n\n    // Returns its internal collection BY REFERENCE: mutations through the accessor persist.\n    Basket\n      items <- List() of String\n\n      items()\n        <- rtn as List of String: items\n\n      default operator ?\n\n    // Returns a fresh COPY each call: mutations through the accessor are lost.\n    SafeBasket\n      contents <- List() of String\n\n      items()\n        <- rtn as List of String: cat contents | collect as List of String\n\n      default operator ?\n\n  defines program\n\n    MutateCallResult()\n      stdout <- Stdout()\n\n      // Mutating operator on a call result that returns the internal field -> mutation PERSISTS\n      basket <- Basket()\n      basket.items() += \"apple\"\n      basket.items() += \"pear\"\n      stdout.println(`reference-return count: ${length basket.items()}`)\n\n      // Same operator on a call result that returns a COPY -> mutation is LOST\n      safe <- SafeBasket()\n      safe.items() += \"apple\"\n      stdout.println(`copy-return count: ${length safe.items()}`)","migrationContext":"Java/Kotlin: obj.getList().add(x) mutates the returned list in place (same aliasing caveat); obj.getList() = x is a compile error (method call is not an lvalue). C++: obj.get() += x works if get() returns a reference (T&) and is meaningless/ill-formed otherwise. EK9: += on a call result is the operator form of a mutating method call and is always allowed; := / :=? on a call result is rejected at compile time (E07895) because there is no assignable target.","keywords":["E07895","accessor","aliasing","assignable","call","collection","getter","in-place","list","lvalue","method","mutating","operator","reference","result","return"],"primaryTopics":["mutating operator on call result","E07895","returned reference mutation"],"typicalErrors":[{"error":"E07895","correct":"      basket.items() += \"apple\"","incorrect":"      basket.items() := List() of String","explanation":"A mutating operator (+=, -=, :~:, :^:, :=:) on a method-call result mutates the returned object in place and is allowed. A simple (:=) or guarded (:=?) assignment has no location to store into - a call result is not an assignable target - so it triggers E07895. See ek9 -h E07895 for details."}],"companions":[]}
{"id":1364,"category":"JSON and Data Processing","question":"How do I read and write CSV in EK9?","url":"https://ek9.io/qa/QA1364.html","alternatePhrasings":["Is CSV a built-in type in EK9?","How do I parse a comma separated values file?","How do I handle quoted CSV fields that contain commas?","How do I access a CSV cell by column name?","How do I write CSV output in EK9?"],"answer":"CSV is a first-class built-in type in EK9 - a full RFC 4180 reader and writer - so you do not reach for split(',') (which breaks the moment a field is quoted) or an external library.\n\nPARSING RETURNS A RESULT\nCSV().parse(text) returns a Result of (CSV, String). A malformed document (for example an unterminated quoted field) becomes an error carrying its reason, and - as with Optional - the compiler REQUIRES you to check isOk() before you call ok():\n  result <- CSV().parse(text)\n  if result.isOk()\n    table <- result.ok()\nThe separator is configurable via a constructor argument, so CSV(';') or CSV('\\t') read semicolon- and tab-separated data.\n\nREADING ROWS AND CELLS\nA CSV holds a header row and the data rows. Read rows total-style, exactly like List - getOrDefault(index, default) never goes out of range:\n  table.rows()                        how many data rows\n  table.header()                      the header CSVRow\n  row <- table.getOrDefault(0, CSVRow())\nA CSVRow is more than a List of String: a data row is header-aware, so a cell can be read by column NAME as well as by position:\n  row.getOrDefault(1, \"?\")            by position\n  row.getOrDefault(\"weight\", \"?\")     by column name\n\nGETTING A REAL TYPE OUT OF A CELL\nCells are text. To get a real type, hand the cell to that type's constructor, which parses it and returns unset if it is not that type - no throwing:\n  weight <- BigDecimal(row.getOrDefault(\"weight\", \"0\"))\n  number <- Integer(row.getOrDefault(\"number\", \"0\"))\nThis is uniform for every type, including your own.\n\nITERATING (AND STREAMS)\nA CSV iterates its data rows, so a for-in loop - or a full stream pipeline (filter, sort, head ...) - works directly:\n  for row in table\n    stdout.println(row.getOrDefault(\"name\", \"?\"))\n\nWRITING\nBuild a CSV row-by-row and render it back to RFC 4180 text with $ (fields that contain the separator, a quote or a newline are quoted for you):\n  out <- CSV()\n  head <- CSVRow()\n  head += \"id\"\n  head += \"label\"\n  built <- out.header(head)\n  row <- CSVRow()\n  row += \"1\"\n  row += \"has, comma\"\n  built += row\n  csvText <- $built\n\nCOMBINING\nStacking two CSVs vertically (+, :~:, +=) is guarded by header alignment - if the two headers are not the same columns the result is unset, so you cannot silently weld mismatched data together.\n\nSee Q188 for JSON as a first-class type. See Q23 for basic types. See Q129 for what happens when a key or index is absent (getOrDefault). Use 'ek9 -h CSV' and 'ek9 -h CSVRow' for the full API.","ek9Example":"defines module qa.csv.readwrite\n\n  defines program\n\n    CsvReadWrite()\n      stdout <- Stdout()\n\n      //Read: a quoted field keeps its comma; parse returns a Result.\n      result <- CSV().parse(\"name,weight\\nHydrogen,1.008\\nHelium,\\\"4.0026, approx\\\"\")\n      if result.isOk()\n        table <- result.ok()\n\n        stdout.println(`rows: ${table.rows()}`)\n\n        //Read a data row and a cell by column name; convert with a constructor.\n        helium <- table.getOrDefault(1, CSVRow())\n        note <- helium.getOrDefault(\"weight\", \"?\")\n        stdout.println(`helium weight cell: ${note}`)\n\n        firstWeight <- BigDecimal(table.getOrDefault(0, CSVRow()).getOrDefault(\"weight\", \"0\"))\n        stdout.println(`hydrogen weight typed: ${firstWeight}`)\n\n        //Iterate the data rows.\n        for row in table\n          stdout.println(row.getOrDefault(\"name\", \"?\"))\n\n      //Write: build a CSV row-by-row and render to RFC 4180 text.\n      out <- CSV()\n      head <- CSVRow()\n      head += \"id\"\n      head += \"label\"\n      built <- out.header(head)\n\n      dataRow <- CSVRow()\n      dataRow += \"1\"\n      dataRow += \"has, comma\"\n      built += dataRow\n\n      stdout.print($built)","migrationContext":"Java: CSV needs an external library (Apache Commons CSV, OpenCSV); split(',') is a common but broken shortcut. Python: csv.reader (positional) / csv.DictReader (by column name); cells are always str. Go: encoding/csv. Rust: the csv crate with serde. EK9: CSV and CSVRow are built in. parse returns Result of (CSV, String) with compiler-enforced isOk() before ok(); rows read by index or column name with getOrDefault; cells typed via the target constructor; writer via $. Full RFC 4180 (quoted fields, doubled quotes, embedded newlines), configurable separator.","keywords":["column","comma","csv","data","field","file","getOrDefault","header","parse","quoted","read","result","rfc4180","row","separated","separator","table","tsv","values","write"],"primaryTopics":["CSV","CSV parsing","read CSV","write CSV"],"typicalErrors":[{"error":"E08030","correct":"if result.isOk()\n  table <- result.ok()","incorrect":"table <- result.ok()","explanation":"CSV.parse returns a Result of (CSV, String). Like Optional, the compiler enforces a check before access: ok() may only be called after isOk(), and error() only after isError(). Calling ok() (or error()) unguarded triggers E08030. Note the 'else' of an isOk() check does NOT count as an isError() check - a Result can also be empty - so guard error() with its own 'if result.isError()'. See ek9 -h E08030."}],"companions":[]}
{"id":1365,"category":"Collections and Data Structures","question":"How do I use a Set for unique values in EK9?","url":"https://ek9.io/qa/QA1365.html","alternatePhrasings":["How do I remove duplicates in EK9?","Is there a Set type in EK9?","How do I test membership efficiently?","How do I get the union or difference of two collections?"],"answer":"Set of type T is a built-in collection of UNIQUE values. Adding a value that is already present has no effect, so it is the natural tool for de-duplication and fast membership testing.\n\nINSERTION-ORDERED\nUnlike a hash set in some languages, an EK9 Set is INSERTION-ORDERED (backed by a linked hash set, just like Dict). Iterating a Set yields values in the order they were first added, which is deterministic and reproducible across runs and JVMs - so output and tests stay stable. Do not rely on any other order.\n\nCREATING AND ADDING\n  seen <- Set() of String\n  seen += \"apple\"\n  seen += \"banana\"\n  seen += \"apple\"      // duplicate - no effect\nUniqueness is by VALUE, using the element type's == and #? (hashcode) operators - the same mechanism that makes a value a valid Dict key.\n\nMEMBERSHIP, SIZE, ITERATION\n  found <- seen contains \"banana\"\n  count <- seen.length()               // reflects the unique count\n  for item in seen                     // first-insertion order\n    stdout.println(item)\nA Set is always set when created; an empty Set is a valid value, not unset.\n\nUNION AND DIFFERENCE\n  combined <- setA + setB              // union\n  removed  <- setA - setB              // difference\nThese also have in-place forms (+=, -=) and merge (:~:).\n\nWHY NOT DICT OF (T, BOOLEAN)?\nBefore Set, membership was faked with Dict of (T, Boolean). Set says what you mean, iterates in insertion order, and its length is the unique count directly.\n\nList and Dict are the other core collections. Use 'ek9 -h Set' for the full API.","ek9Example":"defines module qa.set.unique\n\n  defines program\n\n    SetUnique()\n      stdout <- Stdout()\n\n      //A Set holds unique values; a duplicate add has no effect.\n      seen <- Set() of String\n      seen += \"apple\"\n      seen += \"banana\"\n      seen += \"apple\"\n\n      count <- seen.length()\n      stdout.println(`unique count: ${count}`)\n\n      found <- seen contains \"banana\"\n      stdout.println(`contains banana: ${found}`)\n\n      //Iteration is in first-insertion order (deterministic).\n      for item in seen\n        stdout.println(item)\n\n      //Union with another Set.\n      other <- Set() of String\n      other += \"cherry\"\n      other += \"date\"\n      combined <- seen + other\n      stdout.println(`union count: ${combined.length()}`)","migrationContext":"Java: HashSet (unordered) / LinkedHashSet (insertion order). Python: set (unordered) - EK9 differs by being insertion-ordered. Go: map[T]struct{} idiom. Rust: HashSet / IndexSet. EK9: Set of type T, insertion-ordered by design (like Dict), value-based uniqueness via == and #?, with +/-/+=/-=/contains/length and for-in iteration.","keywords":["collection","contains","dedup","difference","distinct","duplicate","hashset","insertion","membership","order","set","union","unique"],"primaryTopics":["Set","unique values","de-duplication"],"typicalErrors":[{"error":"E05030","correct":"MyThing\n  items as Set of String","incorrect":"MyThing extends Set of String","explanation":"Set (like the other built-in generic collections) is CLOSED - it cannot be extended (E05030: not open to be extended). Extending a collection mixes collection mechanics with application logic. Use composition/delegation instead: hold a 'Set of String' as a field and expose the operations you need."}],"companions":[]}
{"id":1366,"category":"Advanced Type System","question":"How do I work with very large integers (BigInteger) in EK9?","url":"https://ek9.io/qa/QA1366.html","alternatePhrasings":["What do I use when Integer overflows?","Is there arbitrary-precision integer arithmetic in EK9?","How do I compute large factorials or unbounded Fibonacci?"],"answer":"Integer in EK9 is 64-bit, and on overflow it becomes UNSET rather than wrapping. When you need whole numbers larger than that, use BigInteger - an arbitrary-precision integer that never overflows.\n\nCREATING\n  big <- BigInteger(\"123456789012345678901234567890\")\n  fromInt <- BigInteger(1000)     // widen from a 64-bit Integer\n\nARITHMETIC (NEVER OVERFLOWS)\n+, -, * and ^ (power) grow the value as needed:\n  squared <- big * big\n  cubed <- big ^ 3\nComparisons (<, <=, >, >=, ==, <>, <=>), abs and sqrt are available too.\n\nMOD AND REM\nEK9 requires the mod and rem operators to return an Integer, so BigInteger.mod / .rem return a 64-bit Integer; if the result would not fit 64 bits it is returned UNSET rather than losing data.\n\nNO PROMOTION TO FLOAT\nBigInteger deliberately has NO #^ promote operator. A BigInteger is WIDER than a Float, so an implicit conversion would silently lose precision - exactly what BigInteger exists to avoid. If you really need a floating value, do the conversion explicitly.\n\nDISPLAY\n$ gives the full decimal string:\n  stdout.println($big)\n\nSee Q1367 for exact decimals (BigDecimal). Use 'ek9 -h BigInteger' for the full API.","ek9Example":"defines module qa.biginteger.usage\n\n  defines program\n\n    BigIntegerUsage()\n      stdout <- Stdout()\n\n      //Arbitrary precision - multiplication never overflows.\n      big <- BigInteger(\"123456789012345678901234567890\")\n      squared <- big * big\n      stdout.println(`squared: ${squared}`)\n\n      //Widen from a 64-bit Integer.\n      fromInt <- BigInteger(1000)\n      cubed <- fromInt ^ 3\n      stdout.println(`cubed: ${cubed}`)","migrationContext":"Java: java.math.BigInteger. Python: int is arbitrary precision by default. Go: math/big Int. Rust: num-bigint crate. EK9: BigInteger is built in; +,-,*,^ never overflow (contrast Integer which unsets on overflow); mod/rem return Integer (unset if out of 64-bit range); no #^ promote (a BigInteger is wider than Float).","keywords":["arbitrary","arithmetic","big","biginteger","factorial","fibonacci","integer","large","number","overflow","power","precision"],"primaryTopics":["BigInteger","arbitrary precision","large integers"],"typicalErrors":[],"companions":[]}
{"id":1367,"category":"Advanced Type System","question":"How do I get exact decimals (BigDecimal) in EK9?","url":"https://ek9.io/qa/QA1367.html","alternatePhrasings":["Why is 0.1 + 0.2 not 0.3 with Float?","How do I do high-precision decimal arithmetic?","What do I use for exact decimals that are not money?"],"answer":"Float is 64-bit binary floating point, so it cannot represent values like 0.1 exactly (0.1 + 0.2 is not quite 0.3). When you need EXACT decimals, use BigDecimal - arbitrary-precision decimal arithmetic. (For currency specifically, use Money.)\n\nEXACT ARITHMETIC\n  sum <- BigDecimal(\"0.1\") + BigDecimal(\"0.2\")   // exactly 0.3\n+, -, * and ^ are exact. Create from a String (exact), an Integer, or a Float.\n\nEQUALITY IS BY VALUE, NOT SCALE\nUnlike java.math.BigDecimal.equals, EK9 compares by value: 1.0 equals 1.00. ==, <=> and #? all use value comparison, so equal values also hash the same.\n\nDIVISION AND SQRT\nA non-terminating quotient like 1/3 needs a bound, so division and sqrt keep 34 significant digits (MathContext DECIMAL128). Division by zero is UNSET.\n\nROUNDING\n  rounded <- BigDecimal(\"3.14159\").round(2)      // 3.14 (HALF_UP)\n\nDISPLAY\n$ gives the plain-string form (no scientific notation).\n\nLike BigInteger, BigDecimal has no #^ promote operator. See Q1366 for BigInteger and Q23 for the Money type. Use 'ek9 -h BigDecimal' for the full API.","ek9Example":"defines module qa.bigdecimal.usage\n\n  defines program\n\n    BigDecimalUsage()\n      stdout <- Stdout()\n\n      //Exact: 0.1 + 0.2 is exactly 0.3 (Float cannot do this).\n      sum <- BigDecimal(\"0.1\") + BigDecimal(\"0.2\")\n      stdout.println(`sum: ${sum}`)\n\n      //Equality is by value, not scale: 1.0 equals 1.00.\n      a <- BigDecimal(\"1.0\")\n      b <- BigDecimal(\"1.00\")\n      equal <- a == b\n      stdout.println(`1.0 == 1.00: ${equal}`)\n\n      //Round to a number of decimal places.\n      rounded <- BigDecimal(\"3.14159\").round(2)\n      stdout.println(`rounded: ${rounded}`)","migrationContext":"Java: java.math.BigDecimal - but equals is scale-sensitive (1.0 != 1.00); EK9 compares by value. Python: decimal.Decimal. Go: shopspring/decimal or math/big Rat. Rust: rust_decimal. EK9: BigDecimal is built in; exact (0.1+0.2==0.3); value equality (1.0==1.00); DECIMAL128 division; round(places); no #^ promote. Use Money for currency.","keywords":["0.1","0.3","arithmetic","bigdecimal","decimal","equality","exact","float","money","precision","rounding","scale","value"],"primaryTopics":["BigDecimal","exact decimals","decimal precision"],"typicalErrors":[],"companions":[]}
{"id":1368,"category":"Advanced Type System","question":"How do I generate random numbers, and make them reproducible, in EK9?","url":"https://ek9.io/qa/QA1368.html","alternatePhrasings":["How do I seed a random number generator in EK9?","How do I make randomness deterministic in tests?","What is the difference between SystemRandom and SeededRandom?","How do I roll a dice or pick a random value?"],"answer":"Randomness in EK9 is behind the Random trait, with two implementations - following the same injectable pattern as Clock / SystemClock.\n\nTHE RANDOM TRAIT\n  next()                full-range Integer\n  next(lower, upper)    Integer in the inclusive range [lower, upper]\n  nextFloat()           Float in [0.0, 1.0)\n  nextBoolean()         Boolean\n\nTWO IMPLEMENTATIONS\n  SystemRandom()        production - non-deterministic (a different sequence each run)\n  SeededRandom(seed)    reproducible - a given seed yields a PINNED sequence, the same across runs and JVMs\n\nWHY IT MATTERS\nBecause behaviour is behind a trait, code depends on Random and you inject the implementation. In production inject SystemRandom; in tests inject SeededRandom (or a mock) so anything using randomness becomes deterministic and its output can be asserted. Using SystemRandom in a test gives flaky, unrepeatable results - reach for SeededRandom there instead.\n\nEXAMPLE (REPRODUCIBLE DICE)\n  rnd <- SeededRandom(42)\n  d1 <- rnd.next(1, 6)     // inclusive 1..6\n  d2 <- rnd.next(1, 6)\nThe (lower, upper) bounds are inclusive and match OS().random(lower, upper).\n\nNote there is no shuffle/choice primitive - write a small helper over next(l, u) if you need to reorder or pick from a List.\n\nClock / SystemClock is the sibling injectable pair. Use 'ek9 -h SeededRandom' for the full API.","ek9Example":"defines module qa.random.usage\n\n  defines program\n\n    RandomUsage()\n      stdout <- Stdout()\n\n      //Reproducible: the same seed yields the same sequence every run.\n      rnd <- SeededRandom(42)\n\n      d1 <- rnd.next(1, 6)\n      d2 <- rnd.next(1, 6)\n      stdout.println(`dice: ${d1} ${d2}`)\n\n      f <- rnd.nextFloat()\n      stdout.println(`float in [0,1): ${f}`)","migrationContext":"Java: java.util.Random(seed) / ThreadLocalRandom. Python: random.seed / random.Random(seed). Go: math/rand with a seeded Source. Rust: rand crate with StdRng::seed_from_u64. EK9: a Random trait with SystemRandom (non-deterministic) and SeededRandom(seed) (reproducible), modelled on the injectable Clock/SystemClock pair; next()/next(l,u inclusive)/nextFloat/nextBoolean.","keywords":["deterministic","dice","inject","next","random","reproducible","seed","seeded","seededrandom","shuffle","systemrandom","test","trait"],"primaryTopics":["Random","SeededRandom","reproducible randomness"],"typicalErrors":[],"companions":[]}
{"id":1369,"category":"Advanced Type System","question":"How do I do trigonometry and use maths constants in EK9?","url":"https://ek9.io/qa/QA1369.html","alternatePhrasings":["Where are sin, cos, tan in EK9?","How do I get pi and e?","Does EK9 have log and exp?","How do I compute a square root or power?"],"answer":"The trigonometric and transcendental functions live on Float, and the mathematical constants come from the small stateless Maths type.\n\nCONSTANTS (Maths)\n  maths <- Maths()\n  piValue <- maths.pi()\n  eValue <- maths.e()\n\nFUNCTIONS ON FLOAT (RADIANS)\nAll trig works in RADIANS:\n  sin, cos, tan, asin, acos, atan, atan2\n  log (natural), log10, exp\nplus sqrt and abs (operators) and ^ (power). For example:\n  halfPi <- maths.pi() / 2.0\n  s <- halfPi.sin()               // ~ 1.0\n\nUNSET ON OUT-OF-DOMAIN\nA result that is not a finite number becomes UNSET rather than a NaN surprise - for example asin of a value outside -1..1, or log of a non-positive value.\n\nROUNDING\n  rounded <- 3.14159.round(2)      // 3.14\n\nRemember to convert degrees to radians yourself (degrees * pi / 180) before calling the trig functions.\n\nSee Q23 for the Float type basics. Use 'ek9 -h Float' and 'ek9 -h Maths' for the full API.","ek9Example":"defines module qa.maths.usage\n\n  defines program\n\n    MathsUsage()\n      stdout <- Stdout()\n\n      maths <- Maths()\n\n      //Trig works in radians: sin(pi/2) ~ 1.0\n      halfPi <- maths.pi() / 2.0\n      s <- halfPi.sin()\n      stdout.println(`sin(pi/2): ${s}`)\n\n      //Natural log of e is 1.0\n      lnE <- maths.e().log()\n      stdout.println(`ln(e): ${lnE}`)","migrationContext":"Java: java.lang.Math.sin/cos/... and Math.PI/Math.E. Python: math module (math.sin, math.pi). Go: math package. Rust: f64 methods (x.sin()) and std::f64::consts::PI. EK9: trig/transcendental methods on Float (radians) - sin/cos/tan/asin/acos/atan/atan2/log/log10/exp - plus sqrt/abs/^; constants pi()/e() on the Maths type; out-of-domain results are unset.","keywords":["cos","e","exp","float","log","math","maths","pi","power","radians","sin","sqrt","tan","transcendental","trigonometry"],"primaryTopics":["Maths","trigonometry","maths constants"],"typicalErrors":[],"companions":[]}
{"id":1370,"category":"Security and Sanitization","question":"Why does reading a file with TextFile.input() give me unset lines?","url":"https://ek9.io/qa/QA1370.html","alternatePhrasings":["How do I read a file that contains SQL in EK9?","What is the difference between input() and unsanitizedInput() in EK9?","My file lines come back unset when I read them in EK9","How do I bypass sanitization when reading a file in EK9?","How do I find out why a line was rejected in EK9?"],"answer":"TextFile.input() screens every line as it is read. A line that trips a threat detector is REJECTED and comes back as an unset String - it is not passed on. This is the secure default. Use unsanitizedInput() when the file legitimately contains content that looks dangerous.\n\nSCREENED READ - THE DEFAULT\nfile.input() wraps the read so each line passes through InputSanitizer:\n  try\n    -> input <- file.input()\n    while input.hasNext()\n      line <- input.next()\n      if line?\n        stdout.println(line)\n      else\n        stdout.println(\"blocked\")\nAlways test the line with line? before using it. A rejected line is unset.\n\nUNSET MEANS REJECTED, NOTHING ELSE\nWhile hasNext() is true, an unset line can only mean the line was rejected. A blank line or a whitespace-only line reads back SET (an empty or blank String is a set String), so there is never any confusion between \"rejected\" and \"empty\".\n\nUNSCREENED READ - THE BYPASS\nSome files legitimately contain what looks like an attack: SQL migration scripts, HTML fragments, shell scripts, log files containing --. Screening those would discard real data, so read them with unsanitizedInput():\n  try\n    -> rawInput <- file.unsanitizedInput()\n    while rawInput.hasNext()\n      line <- rawInput.next()\n      stdout.println(line)\nEvery line passes through unchanged.\n\nGETTING THE REASON, NOT JUST THE FACT\ninput() tells you a line was rejected but not why - the threat category goes to the sanitizer log. When the program itself needs the category, read raw and ask InputSanitizer directly:\n  sanitizer <- InputSanitizer()\n  threat <- sanitizer.detectThreat(line)\n  if threat?\n    stdout.println(threat)\ndetectThreat() returns a comma separated list such as \"SQL_INJECTION, SQL_INJECTION_SIGNATURE\" and, unlike sanitize(), does not write to the log. It returns an unset String when the line is safe.\n\nSCREENING IS A JUDGEMENT, NOT A PROOF\nDetection is heuristic in both directions. That is why the bypass exists, and why the real guarantee comes from the compiler-enforced sanitized modifier at the point a String enters a dangerous context - not from the file read.\n\nSee Q151 for reading text files. See Q1237 for sanitized function parameters. See Q1246 for sanitized constructor parameters. See Q1247 for sanitized method parameters.","ek9Example":"defines module qa.security.sanitizedread\n\n  defines program\n\n    SanitizedVsUnsanitizedRead()\n      stdout <- Stdout()\n\n      file <- TextFile(\"/tmp/qa-example-mixed.txt\")\n      stdout.println(\"File: \" + $file)\n\n      // === SCREENED READ - THE DEFAULT ===\n\n      // input() screens every line. A rejected line comes back UNSET,\n      // so always test it with line? before using the value.\n      stdout.println(\"Screened read\")\n      try\n        -> input <- file.input()\n        while input.hasNext()\n          line <- input.next()\n          if line?\n            stdout.println(line)\n          else\n            stdout.println(\"blocked\")\n\n      // === UNSCREENED READ - THE BYPASS ===\n\n      // Use unsanitizedInput() for files that legitimately hold content\n      // resembling an attack: SQL scripts, HTML, shell scripts, logs with --\n      stdout.println(\"Unscreened read\")\n      try\n        -> rawInput <- file.unsanitizedInput()\n        while rawInput.hasNext()\n          line <- rawInput.next()\n          stdout.println(line)\n\n      // === THE REASON, NOT JUST THE FACT ===\n\n      // input() reports THAT a line was rejected; detectThreat reports WHY.\n      // It returns an unset String for safe content and does not write a log entry.\n      sanitizer <- InputSanitizer()\n      stdout.println(\"Threat categories\")\n      try\n        -> rawInput <- file.unsanitizedInput()\n        while rawInput.hasNext()\n          line <- rawInput.next()\n          threat <- sanitizer.detectThreat(line)\n          if threat?\n            stdout.println(threat)","migrationContext":"Java/Python/Go: reading a file returns bytes or lines with no screening at all; any input validation is something you remember to add. EK9: TextFile.input() screens by default and reports a rejection as an unset String, so forgetting to check is visible rather than silent. unsanitizedInput() is the deliberate, named bypass, and InputSanitizer.detectThreat() supplies the threat category when the program needs to branch on it.","keywords":["blocked","bypass","file","injection","input","read","rejected","sanitize","security","textfile","threat","unsanitized","unset"],"primaryTopics":["sanitized file read","unsanitizedInput","detectThreat"],"typicalErrors":[{"error":"E50060","correct":"line <- input.next()\n      if line?\n        stdout.println(line)","incorrect":"line <- input.next()\n      stdout.println(line)","explanation":"input() screens each line, so a rejected line is unset. Test it with line? before use, or read via unsanitizedInput() if the content is trusted. See ek9 -h E50060 for details."}],"companions":[]}
